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 MultiParamTypeClasses #-}
19 {-# LANGUAGE NoImplicitPrelude #-}
20 {-# LANGUAGE TemplateHaskell #-}
22 module Gargantext.Database.Node where
25 import GHC.Int (Int64)
27 import Data.Time (UTCTime)
28 import Database.PostgreSQL.Simple.FromField ( Conversion
29 , ResultError(ConversionFailed)
34 import Prelude hiding (null, id, map, sum)
36 import Gargantext.Core.Types
37 import Gargantext.Core.Types.Node (NodeType)
38 import Gargantext.Database.Queries
39 import Gargantext.Prelude hiding (sum)
42 import Database.PostgreSQL.Simple.Internal (Field)
43 import Control.Arrow (returnA)
44 import Control.Lens.TH (makeLensesWith, abbreviatedFields)
46 import Data.Maybe (Maybe, fromMaybe)
47 import Data.Text (Text, pack)
48 import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
49 import Data.Typeable (Typeable)
51 import qualified Data.ByteString as DB
52 import qualified Data.ByteString.Lazy as DBL
53 import Data.ByteString (ByteString)
55 import Database.PostgreSQL.Simple (Connection)
56 import Opaleye hiding (FromField)
57 import Opaleye.Internal.QueryArr (Query)
58 import qualified Data.Profunctor.Product as PP
59 -- | Types for Node Database Management
62 ------------------------------------------------------------------------
64 ------------------------------------------------------------------------
66 instance FromField HyperdataCorpus where
67 fromField = fromField'
69 instance FromField HyperdataDocument where
70 fromField = fromField'
72 instance FromField HyperdataDocumentV3 where
73 fromField = fromField'
75 instance FromField HyperdataProject where
76 fromField = fromField'
78 instance FromField HyperdataUser where
79 fromField = fromField'
82 instance QueryRunnerColumnDefault PGJsonb HyperdataDocument where
83 queryRunnerColumnDefault = fieldQueryRunnerColumn
84 instance QueryRunnerColumnDefault PGJsonb HyperdataDocumentV3 where
85 queryRunnerColumnDefault = fieldQueryRunnerColumn
87 instance QueryRunnerColumnDefault PGJsonb HyperdataCorpus where
88 queryRunnerColumnDefault = fieldQueryRunnerColumn
90 instance QueryRunnerColumnDefault PGJsonb HyperdataProject where
91 queryRunnerColumnDefault = fieldQueryRunnerColumn
93 instance QueryRunnerColumnDefault PGJsonb HyperdataUser where
94 queryRunnerColumnDefault = fieldQueryRunnerColumn
98 fromField' :: (Typeable b, FromJSON b) => Field -> Maybe DB.ByteString -> Conversion b
99 fromField' field mb = do
100 v <- fromField field mb
103 valueToHyperdata v = case fromJSON v of
105 Error _err -> returnError ConversionFailed field "cannot parse hyperdata"
108 $(makeAdaptorAndInstance "pNode" ''NodePoly)
109 $(makeLensesWith abbreviatedFields ''NodePoly)
112 nodeTable :: Table NodeWrite NodeRead
113 nodeTable = Table "nodes" (pNode Node { node_id = optional "id"
114 , node_typename = required "typename"
115 , node_userId = required "user_id"
116 , node_parentId = required "parent_id"
117 , node_name = required "name"
118 , node_date = optional "date"
119 , node_hyperdata = required "hyperdata"
120 -- , node_titleAbstract = optional "title_abstract"
125 nodeTable' :: Table (Maybe (Column PGInt4)
130 ,Maybe (Column PGTimestamptz)
138 ,(Column PGTimestamptz)
142 nodeTable' = Table "nodes" (PP.p7 ( optional "id"
143 , required "typename"
145 , required "parent_id"
148 , required "hyperdata"
153 queryNodeTable :: Query NodeRead
154 queryNodeTable = queryTable nodeTable
157 selectNode :: Column PGInt4 -> Query NodeRead
158 selectNode id = proc () -> do
159 row <- queryNodeTable -< ()
160 restrict -< node_id row .== id
163 runGetNodes :: Connection -> Query NodeRead -> IO [Node Value]
164 runGetNodes = runQuery
166 -- | order by publication date
167 -- Favorites (Bool), node_ngrams
168 selectNodesWith :: ParentId -> Maybe NodeType
169 -> Maybe Offset -> Maybe Limit -> Query NodeRead
170 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
171 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
172 limit' maybeLimit $ offset' maybeOffset $ orderBy (asc node_id) $ selectNodesWith' parentId maybeNodeType
174 selectNodesWith' :: ParentId -> Maybe NodeType -> Query NodeRead
175 selectNodesWith' parentId maybeNodeType = proc () -> do
176 node <- (proc () -> do
177 row@(Node _ typeId _ parentId' _ _ _) <- queryNodeTable -< ()
178 restrict -< parentId' .== (toNullable $ pgInt4 parentId)
180 let typeId' = maybe 0 nodeTypeId maybeNodeType
182 restrict -< if typeId' > 0
183 then typeId .== (pgInt4 (typeId' :: Int))
185 returnA -< row ) -< ()
190 deleteNode :: Connection -> Int -> IO Int
191 deleteNode conn n = fromIntegral <$> runDelete conn nodeTable
192 (\(Node n_id _ _ _ _ _ _) -> n_id .== pgInt4 n)
194 deleteNodes :: Connection -> [Int] -> IO Int
195 deleteNodes conn ns = fromIntegral <$> runDelete conn nodeTable
196 (\(Node n_id _ _ _ _ _ _) -> in_ ((map pgInt4 ns)) n_id)
199 getNodesWith :: Connection -> Int -> Maybe NodeType
200 -> Maybe Offset -> Maybe Limit -> IO [Node Value]
201 getNodesWith conn parentId nodeType maybeOffset maybeLimit =
202 runQuery conn $ selectNodesWith
203 parentId nodeType maybeOffset maybeLimit
207 getNodesWithParentId :: Connection -> Int
208 -> Maybe Text -> IO [Node Value]
209 getNodesWithParentId conn n _ = runQuery conn $ selectNodesWithParentID n
211 getNodesWithParentId' :: Connection -> Int
212 -> Maybe Text -> IO [Node Value]
213 getNodesWithParentId' conn n _ = runQuery conn $ selectNodesWithParentID n
217 ------------------------------------------------------------------------
218 getDocumentsV3WithParentId :: Connection -> Int -> IO [Node HyperdataDocumentV3]
219 getDocumentsV3WithParentId conn n = runQuery conn $ selectNodesWith' n (Just Document)
221 getDocumentsWithParentId :: Connection -> Int -> IO [Node HyperdataDocument]
222 getDocumentsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just Document)
224 ------------------------------------------------------------------------
227 selectNodesWithParentID :: Int -> Query NodeRead
228 selectNodesWithParentID n = proc () -> do
229 row@(Node _ _ _ parent_id _ _ _) <- queryNodeTable -< ()
232 parent_id .== (toNullable $ pgInt4 n)
238 selectNodesWithType :: Column PGInt4 -> Query NodeRead
239 selectNodesWithType type_id = proc () -> do
240 row@(Node _ tn _ _ _ _ _) <- queryNodeTable -< ()
241 restrict -< tn .== type_id
245 getNode :: Connection -> Int -> IO (Node Value)
247 fromMaybe (error $ "Node does node exist: " <> show id) . headMay <$> runQuery conn (limit 1 $ selectNode (pgInt4 id))
250 getNodesWithType :: Connection -> Column PGInt4 -> IO [Node HyperdataDocument]
251 getNodesWithType conn type_id = do
252 runQuery conn $ selectNodesWithType type_id
258 ------------------------------------------------------------------------
260 ------------------------------------------------------------------------
261 type NodeWrite' = NodePoly (Maybe Int) Int Int (ParentId) Text (Maybe UTCTime) ByteString
263 --node :: UserId -> ParentId -> NodeType -> Text -> Value -> NodeWrite'
264 node :: UserId -> ParentId -> NodeType -> Text -> Value -> NodeWrite'
265 node userId parentId nodeType name nodeData = Node Nothing typeId userId parentId name Nothing byteData
267 typeId = nodeTypeId nodeType
268 byteData = DB.pack $ DBL.unpack $ encode nodeData
272 node2write :: (Functor f2, Functor f1) =>
274 -> NodePoly (f1 Int) Int Int parentId Text (f2 UTCTime) ByteString
275 -> (f1 (Column PGInt4), Column PGInt4, Column PGInt4,
276 Column PGInt4, Column PGText, f2 (Column PGTimestamptz),
278 node2write pid (Node id tn ud _ nm dt hp) = ((pgInt4 <$> id)
288 mkNode :: Connection -> ParentId -> [NodeWrite'] -> IO Int64
289 mkNode conn pid ns = runInsertMany conn nodeTable' $ map (node2write pid) ns
291 mkNodeR :: Connection -> ParentId -> [NodeWrite'] -> IO [Int]
292 mkNodeR conn pid ns = runInsertManyReturning conn nodeTable' (map (node2write pid) ns) (\(i,_,_,_,_,_,_) -> i)
295 ------------------------------------------------------------------------
296 -- TODO Hierachy of Nodes
297 -- post and get same types Node' and update if changes
299 {- TODO semantic to achieve
300 post c uid pid [ Node' Corpus "name" "{}" []
301 , Node' Folder "name" "{}" [Node' Corpus "test 2" "" [ Node' Document "title" "metaData" []
302 , Node' Document "title" "jsonData" []
307 ------------------------------------------------------------------------
310 -- currently this function remove the child relation
311 -- needs a Temporary type between Node' and NodeWriteT
312 node2table :: UserId -> ParentId -> Node' -> [NodeWriteT]
313 node2table uid pid (Node' nt txt v []) = [( Nothing, (pgInt4$ nodeTypeId nt), (pgInt4 uid), (pgInt4 pid)
314 , pgStrictText txt, Nothing, pgStrictJSONB $ DB.pack $ DBL.unpack $ encode v)]
315 node2table _ _ (Node' _ _ _ _) = panic $ pack "node2table: should not happen, Tree insert not implemented yet"
318 data Node' = Node' { _n_type :: NodeType
321 , _n_children :: [Node']
325 type NodeWriteT = ( Maybe (Column PGInt4)
326 , Column PGInt4, Column PGInt4
327 , Column PGInt4, Column PGText
328 , Maybe (Column PGTimestamptz)
333 mkNode' :: Connection -> [NodeWriteT] -> IO Int64
334 mkNode' conn ns = runInsertMany conn nodeTable' ns
336 mkNodeR' :: Connection -> [NodeWriteT] -> IO [Int]
337 mkNodeR' conn ns = runInsertManyReturning conn nodeTable' ns (\(i,_,_,_,_,_,_) -> i)
340 postNode :: Connection -> UserId -> ParentId -> Node' -> IO [Int]
341 postNode c uid pid (Node' nt txt v []) = mkNodeR' c (node2table uid pid (Node' nt txt v []))
343 postNode c uid pid (Node' NodeCorpus txt v ns) = do
344 [pid'] <- postNode c uid pid (Node' NodeCorpus txt v [])
345 pids <- mkNodeR' c $ concat $ map (\n -> childWith uid pid' n) ns
348 postNode c uid pid (Node' Annuaire txt v ns) = do
349 [pid'] <- postNode c uid pid (Node' Annuaire txt v [])
350 pids <- mkNodeR' c $ concat $ map (\n -> childWith uid pid' n) ns
352 postNode _ _ _ (Node' _ _ _ _) = panic $ pack "postNode for this type not implemented yet"
355 childWith :: UserId -> ParentId -> Node' -> [NodeWriteT]
356 childWith uId pId (Node' Document txt v []) = node2table uId pId (Node' Document txt v [])
357 childWith uId pId (Node' UserPage txt v []) = node2table uId pId (Node' UserPage txt v [])
358 childWith _ _ (Node' _ _ _ _) = panic $ pack "This NodeType can not be a child"