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 FlexibleInstances #-}
17 {-# LANGUAGE FunctionalDependencies #-}
18 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
19 {-# LANGUAGE OverloadedStrings #-}
20 {-# LANGUAGE MultiParamTypeClasses #-}
21 {-# LANGUAGE NoImplicitPrelude #-}
22 {-# LANGUAGE TemplateHaskell #-}
24 module Gargantext.Database.Node where
26 import Data.Text (pack)
27 import GHC.Int (Int64)
28 import Control.Lens (set)
30 import Data.Time (UTCTime)
31 import Database.PostgreSQL.Simple.FromField ( Conversion
32 , ResultError(ConversionFailed)
37 import Prelude hiding (null, id, map, sum)
39 import Gargantext.Core (Lang(..))
40 import Gargantext.Core.Types
41 import Gargantext.Database.Types.Node (NodeType)
42 import Gargantext.Database.Queries
43 import Gargantext.Database.Config (nodeTypeId)
44 import Gargantext.Prelude hiding (sum)
46 import Database.PostgreSQL.Simple.Internal (Field)
47 import Control.Applicative (Applicative)
48 import Control.Arrow (returnA)
49 import Control.Lens.TH (makeLensesWith, abbreviatedFields)
50 import Control.Monad.IO.Class
51 import Control.Monad.Reader
53 import Data.Maybe (Maybe, fromMaybe)
54 import Data.Text (Text)
55 import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
56 import Data.Typeable (Typeable)
58 import qualified Data.ByteString as DB
59 import qualified Data.ByteString.Lazy as DBL
60 import Data.ByteString (ByteString)
62 import Database.PostgreSQL.Simple (Connection)
63 import Opaleye hiding (FromField)
64 import Opaleye.Internal.QueryArr (Query)
65 import qualified Data.Profunctor.Product as PP
67 ------------------------------------------------------------------------
68 {- | Reader Monad reinvented here:
70 newtype Cmd a = Cmd { unCmd :: Connection -> IO a }
72 instance Monad Cmd where
73 return a = Cmd $ \_ -> return a
75 m >>= f = Cmd $ \c -> do
79 newtype Cmd a = Cmd (ReaderT Connection IO a)
80 deriving (Functor, Applicative, Monad, MonadReader Connection, MonadIO)
82 runCmd :: Connection -> Cmd a -> IO a
83 runCmd c (Cmd f) = runReaderT f c
85 mkCmd :: (Connection -> IO a) -> Cmd a
88 ------------------------------------------------------------------------
93 ------------------------------------------------------------------------
94 instance FromField HyperdataCorpus where
95 fromField = fromField'
97 instance FromField HyperdataDocument where
98 fromField = fromField'
100 instance FromField HyperdataDocumentV3 where
101 fromField = fromField'
103 instance FromField HyperdataUser where
104 fromField = fromField'
105 ------------------------------------------------------------------------
106 instance QueryRunnerColumnDefault PGJsonb HyperdataDocument where
107 queryRunnerColumnDefault = fieldQueryRunnerColumn
109 instance QueryRunnerColumnDefault PGJsonb HyperdataDocumentV3 where
110 queryRunnerColumnDefault = fieldQueryRunnerColumn
112 instance QueryRunnerColumnDefault PGJsonb HyperdataCorpus where
113 queryRunnerColumnDefault = fieldQueryRunnerColumn
115 instance QueryRunnerColumnDefault PGJsonb HyperdataUser where
116 queryRunnerColumnDefault = fieldQueryRunnerColumn
117 ------------------------------------------------------------------------
119 fromField' :: (Typeable b, FromJSON b) => Field -> Maybe DB.ByteString -> Conversion b
120 fromField' field mb = do
121 v <- fromField field mb
124 valueToHyperdata v = case fromJSON v of
126 Error _err -> returnError ConversionFailed field "cannot parse hyperdata"
129 $(makeAdaptorAndInstance "pNode" ''NodePoly)
130 $(makeLensesWith abbreviatedFields ''NodePoly)
133 nodeTable :: Table NodeWrite NodeRead
134 nodeTable = Table "nodes" (pNode Node { _node_id = optional "id"
135 , _node_typename = required "typename"
136 , _node_userId = required "user_id"
137 , _node_parentId = required "parent_id"
138 , _node_name = required "name"
139 , _node_date = optional "date"
140 , _node_hyperdata = required "hyperdata"
141 -- , node_titleAbstract = optional "title_abstract"
146 nodeTable' :: Table (Maybe (Column PGInt4)
149 ,Maybe (Column PGInt4)
151 ,Maybe (Column PGTimestamptz)
159 ,(Column PGTimestamptz)
163 nodeTable' = Table "nodes" (PP.p7 ( optional "id"
164 , required "typename"
166 , optional "parent_id"
169 , required "hyperdata"
174 queryNodeTable :: Query NodeRead
175 queryNodeTable = queryTable nodeTable
177 selectNode :: Column PGInt4 -> Query NodeRead
178 selectNode id = proc () -> do
179 row <- queryNodeTable -< ()
180 restrict -< _node_id row .== id
183 runGetNodes :: Query NodeRead -> Cmd [Node Value]
184 runGetNodes q = mkCmd $ \conn -> runQuery conn q
186 ------------------------------------------------------------------------
187 selectRootUser :: UserId -> Query NodeRead
188 selectRootUser userId = proc () -> do
189 row <- queryNodeTable -< ()
190 restrict -< _node_userId row .== (pgInt4 userId)
191 restrict -< _node_typename row .== (pgInt4 $ nodeTypeId NodeUser)
194 getRoot :: UserId -> Cmd [Node HyperdataUser]
195 getRoot userId = mkCmd $ \conn -> runQuery conn (selectRootUser userId)
196 ------------------------------------------------------------------------
198 -- | order by publication date
199 -- Favorites (Bool), node_ngrams
200 selectNodesWith :: ParentId -> Maybe NodeType
201 -> Maybe Offset -> Maybe Limit -> Query NodeRead
202 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
203 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
204 limit' maybeLimit $ offset' maybeOffset $ orderBy (asc _node_id) $ selectNodesWith' parentId maybeNodeType
206 selectNodesWith' :: ParentId -> Maybe NodeType -> Query NodeRead
207 selectNodesWith' parentId maybeNodeType = proc () -> do
208 node <- (proc () -> do
209 row@(Node _ typeId _ parentId' _ _ _) <- queryNodeTable -< ()
210 restrict -< parentId' .== (toNullable $ pgInt4 parentId)
212 let typeId' = maybe 0 nodeTypeId maybeNodeType
214 restrict -< if typeId' > 0
215 then typeId .== (pgInt4 (typeId' :: Int))
217 returnA -< row ) -< ()
221 --type Cmd' a = forall m. (MonadReader env m, HasConnection env, MonadIO m) => m a
224 -- deleteNode :: (MonadReader Connection m, MonadIO m) => Int -> m Int
225 -- deleteNode :: Int -> Cmd' Int
227 deleteNode :: Int -> Cmd Int
228 deleteNode n = mkCmd $ \conn ->
229 fromIntegral <$> runDelete conn nodeTable
230 (\(Node n_id _ _ _ _ _ _) -> n_id .== pgInt4 n)
232 deleteNodes :: [Int] -> Cmd Int
233 deleteNodes ns = mkCmd $ \conn ->
234 fromIntegral <$> runDelete conn nodeTable
235 (\(Node n_id _ _ _ _ _ _) -> in_ ((map pgInt4 ns)) n_id)
238 getNodesWith :: Connection -> Int -> Maybe NodeType
239 -> Maybe Offset -> Maybe Limit -> IO [Node Value]
240 getNodesWith conn parentId nodeType maybeOffset maybeLimit =
241 runQuery conn $ selectNodesWith
242 parentId nodeType maybeOffset maybeLimit
246 getNodesWithParentId :: Int
247 -> Maybe Text -> Connection -> IO [Node Value]
248 getNodesWithParentId n _ conn = runQuery conn $ selectNodesWithParentID n
250 getNodesWithParentId' :: Int
251 -> Maybe Text -> Connection -> IO [Node Value]
252 getNodesWithParentId' n _ conn = runQuery conn $ selectNodesWithParentID n
255 ------------------------------------------------------------------------
256 getDocumentsV3WithParentId :: Connection -> Int -> IO [Node HyperdataDocumentV3]
257 getDocumentsV3WithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
259 getDocumentsWithParentId :: Connection -> Int -> IO [Node HyperdataDocument]
260 getDocumentsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
262 ------------------------------------------------------------------------
265 selectNodesWithParentID :: Int -> Query NodeRead
266 selectNodesWithParentID n = proc () -> do
267 row@(Node _ _ _ parent_id _ _ _) <- queryNodeTable -< ()
270 parent_id .== (toNullable $ pgInt4 n)
276 selectNodesWithType :: Column PGInt4 -> Query NodeRead
277 selectNodesWithType type_id = proc () -> do
278 row@(Node _ tn _ _ _ _ _) <- queryNodeTable -< ()
279 restrict -< tn .== type_id
283 getNode :: Connection -> Int -> IO (Node Value)
285 fromMaybe (error $ "Node does node exist: " <> show id) . headMay <$> runQuery conn (limit 1 $ selectNode (pgInt4 id))
288 getNodesWithType :: Connection -> Column PGInt4 -> IO [Node HyperdataDocument]
289 getNodesWithType conn type_id = do
290 runQuery conn $ selectNodesWithType type_id
293 ------------------------------------------------------------------------
295 -- TODO Classe HasDefault where
296 -- default NodeType = Hyperdata
297 ------------------------------------------------------------------------
298 type NodeWrite' = NodePoly (Maybe Int) Int Int (Maybe ParentId) Text (Maybe UTCTime) ByteString
299 ------------------------------------------------------------------------
300 defaultUser :: HyperdataUser
301 defaultUser = HyperdataUser (Just $ (pack . show) EN)
303 nodeUserW :: Maybe Name -> Maybe HyperdataUser -> UserId -> NodeWrite'
304 nodeUserW maybeName maybeHyperdata = node NodeUser name (Hyperdata user) Nothing
306 name = maybe "User" identity maybeName
307 user = maybe defaultUser identity maybeHyperdata
308 ------------------------------------------------------------------------
309 defaultFolder :: HyperdataFolder
310 defaultFolder = HyperdataFolder (Just "Markdown Description")
312 nodeFolderW :: Maybe Name -> Maybe HyperdataFolder -> ParentId -> UserId -> NodeWrite'
313 nodeFolderW maybeName maybeFolder pid = node NodeFolder name (Hyperdata folder) (Just pid)
315 name = maybe "Folder" identity maybeName
316 folder = maybe defaultFolder identity maybeFolder
317 ------------------------------------------------------------------------
318 defaultCorpus :: HyperdataCorpus
319 defaultCorpus = (HyperdataCorpus (Just "Title") (Just "Descr") (Just "Bool query") (Just "Authors") Nothing)
321 nodeCorpusW :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> NodeWrite'
322 nodeCorpusW maybeName maybeCorpus pId = node NodeCorpus name (Hyperdata corpus) (Just pId)
324 name = maybe "Corpus" identity maybeName
325 corpus = maybe defaultCorpus identity maybeCorpus
326 --------------------------
327 defaultDocument :: HyperdataDocument
328 defaultDocument = hyperdataDocument
330 nodeDocumentW :: Maybe Name -> Maybe HyperdataDocument -> CorpusId -> UserId -> NodeWrite'
331 nodeDocumentW maybeName maybeDocument cId = node NodeDocument name (Hyperdata doc) (Just cId)
333 name = maybe "Document" identity maybeName
334 doc = maybe defaultDocument identity maybeDocument
335 ------------------------------------------------------------------------
336 defaultAnnuaire :: HyperdataAnnuaire
337 defaultAnnuaire = HyperdataAnnuaire (Just "Title") (Just "Description")
339 nodeAnnuaireW :: Maybe Name -> Maybe HyperdataAnnuaire -> ParentId -> UserId -> NodeWrite'
340 nodeAnnuaireW maybeName maybeAnnuaire pId = node NodeAnnuaire name (Hyperdata annuaire) (Just pId)
342 name = maybe "Annuaire" identity maybeName
343 annuaire = maybe defaultAnnuaire identity maybeAnnuaire
344 --------------------------
345 defaultContact :: HyperdataContact
346 defaultContact = HyperdataContact (Just "Name") (Just "email@here")
348 nodeContactW :: Maybe Name -> Maybe HyperdataContact -> AnnuaireId -> UserId -> NodeWrite'
349 nodeContactW maybeName maybeContact aId = node NodeContact name (Hyperdata contact) (Just aId)
351 name = maybe "Contact" identity maybeName
352 contact = maybe defaultContact identity maybeContact
353 ------------------------------------------------------------------------
354 ------------------------------------------------------------------------
355 node :: ToJSON a => NodeType -> Name -> Hyperdata a -> Maybe ParentId -> UserId -> NodeWrite'
356 node nodeType name hyperData parentId userId = Node Nothing typeId userId parentId name Nothing byteData
358 typeId = nodeTypeId nodeType
359 byteData = DB.pack $ DBL.unpack $ encode $ unHyperdata hyperData
361 -------------------------------
362 node2row :: (Functor maybe1, Functor maybe2, Functor maybe3) =>
363 NodePoly (maybe2 Int) Int Int (maybe1 Int)
364 Text (maybe3 UTCTime) ByteString
365 -> (maybe2 (Column PGInt4), Column PGInt4, Column PGInt4, maybe1 (Column PGInt4)
366 , Column PGText, maybe3 (Column PGTimestamptz), Column PGJsonb)
367 node2row (Node id tn ud pid nm dt hp) = ((pgInt4 <$> id)
375 ------------------------------------------------------------------------
376 insertNodesR' :: [NodeWrite'] -> Cmd [Int]
377 insertNodesR' ns = mkCmd $ \c -> insertNodesR ns c
379 insertNodes :: [NodeWrite'] -> Connection -> IO Int64
380 insertNodes ns conn = runInsertMany conn nodeTable' (map node2row ns)
382 insertNodesR :: [NodeWrite'] -> Connection -> IO [Int]
383 insertNodesR ns conn = runInsertManyReturning conn nodeTable' (map node2row ns) (\(i,_,_,_,_,_,_) -> i)
384 -------------------------
385 insertNodesWithParent :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO Int64
386 insertNodesWithParent pid ns conn = insertNodes (map (set node_parentId pid) ns) conn
388 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO [Int]
389 insertNodesWithParentR pid ns conn = insertNodesR (map (set node_parentId pid) ns) conn
390 ------------------------------------------------------------------------
391 -- TODO Hierachy of Nodes
392 -- post and get same types Node' and update if changes
394 {- TODO semantic to achieve
395 post c uid pid [ Node' NodeCorpus "name" "{}" []
396 , Node' NodeFolder "name" "{}" [Node' NodeCorpus "test 2" "" [ Node' NodeDocument "title" "metaData" []
397 , Node' NodeDocument "title" "jsonData" []
402 ------------------------------------------------------------------------
405 -- currently this function remove the child relation
406 -- needs a Temporary type between Node' and NodeWriteT
407 node2table :: UserId -> Maybe ParentId -> Node' -> NodeWriteT
408 node2table uid pid (Node' nt txt v []) = ( Nothing, (pgInt4$ nodeTypeId nt), (pgInt4 uid), (fmap pgInt4 pid)
409 , pgStrictText txt, Nothing, pgStrictJSONB $ DB.pack $ DBL.unpack $ encode v)
410 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
413 data Node' = Node' { _n_type :: NodeType
416 , _n_children :: [Node']
420 type NodeWriteT = ( Maybe (Column PGInt4)
423 , Maybe (Column PGInt4)
425 , Maybe (Column PGTimestamptz)
430 mkNode' :: [NodeWriteT] -> Cmd Int64
431 mkNode' ns = mkCmd $ \conn -> runInsertMany conn nodeTable' ns
433 mkNodeR' :: [NodeWriteT] -> Cmd [Int]
434 mkNodeR' ns = mkCmd $ \conn -> runInsertManyReturning conn nodeTable' ns (\(i,_,_,_,_,_,_) -> i)
437 ------------------------------------------------------------------------
439 data NewNode = NewNode { _newNodeId :: Int
440 , _newNodeChildren :: [Int] }
443 postNode :: UserId -> Maybe ParentId -> Node' -> Cmd NewNode
444 postNode uid pid (Node' nt txt v []) = do
445 pids <- mkNodeR' [node2table uid pid (Node' nt txt v [])]
447 [pid] -> pure $ NewNode pid []
448 _ -> panic "postNode: only one pid expected"
450 postNode uid pid (Node' NodeCorpus txt v ns) = do
451 NewNode pid' _ <- postNode uid pid (Node' NodeCorpus txt v [])
452 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
453 pure $ NewNode pid' pids
455 postNode uid pid (Node' NodeAnnuaire txt v ns) = do
456 NewNode pid' _ <- postNode uid pid (Node' NodeAnnuaire txt v [])
457 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
458 pure $ NewNode pid' pids
459 postNode _ _ (Node' _ _ _ _) = panic "TODO: postNode for this type not implemented yet"
462 childWith :: UserId -> ParentId -> Node' -> NodeWriteT
463 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
464 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
465 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
468 -- TODO: remove hardcoded userId (with Reader)
469 -- TODO: user Reader in the API and adapt this function
472 mk :: Connection -> NodeType -> Maybe ParentId -> Text -> IO [Int]
473 mk c nt pId name = mk' c nt userId pId name
475 mk' :: Connection -> NodeType -> UserId -> Maybe ParentId -> Text -> IO [Int]
476 mk' c nt uId pId name = map fromIntegral <$> insertNodesWithParentR pId [node nt name hd pId uId] c
478 hd = Hyperdata (HyperdataUser (Just $ (pack . show) EN))
482 mk'' :: NodeType -> Maybe ParentId -> UserId -> Name -> Cmd [Int]
483 mk'' NodeUser Nothing uId name = mkCmd $ \c -> mk' c NodeUser uId Nothing name
484 mk'' NodeUser _ _ _ = panic "NodeUser can not has a parent"
485 mk'' _ Nothing _ _ = panic "NodeType needs a parent"
486 mk'' nt pId uId name = mkCmd $ \c -> mk' c nt uId pId name
488 mkRoot :: UserId -> Cmd [Int]
489 mkRoot uId = case uId > 0 of
490 False -> panic "UserId <= 0"
491 True -> mk'' NodeUser Nothing uId ("User Node : " <> (pack . show) uId)
493 mkCorpus :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> Cmd [Int]
494 mkCorpus n h p u = insertNodesR' [nodeCorpusW n h p u]