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
63 instance FromField HyperdataCorpus where
64 fromField = fromField'
66 instance FromField HyperdataDocument where
67 fromField = fromField'
69 instance FromField HyperdataProject where
70 fromField = fromField'
72 instance FromField HyperdataUser where
73 fromField = fromField'
76 instance QueryRunnerColumnDefault PGJsonb HyperdataDocument where
77 queryRunnerColumnDefault = fieldQueryRunnerColumn
79 instance QueryRunnerColumnDefault PGJsonb HyperdataCorpus where
80 queryRunnerColumnDefault = fieldQueryRunnerColumn
82 instance QueryRunnerColumnDefault PGJsonb HyperdataProject where
83 queryRunnerColumnDefault = fieldQueryRunnerColumn
85 instance QueryRunnerColumnDefault PGJsonb HyperdataUser where
86 queryRunnerColumnDefault = fieldQueryRunnerColumn
90 fromField' :: (Typeable b, FromJSON b) => Field -> Maybe DB.ByteString -> Conversion b
91 fromField' field mb = do
92 v <- fromField field mb
95 valueToHyperdata v = case fromJSON v of
97 Error _err -> returnError ConversionFailed field "cannot parse hyperdata"
100 $(makeAdaptorAndInstance "pNode" ''NodePoly)
101 $(makeLensesWith abbreviatedFields ''NodePoly)
104 nodeTable :: Table NodeWrite NodeRead
105 nodeTable = Table "nodes" (pNode Node { node_id = optional "id"
106 , node_typename = required "typename"
107 , node_userId = required "user_id"
108 , node_parentId = required "parent_id"
109 , node_name = required "name"
110 , node_date = optional "date"
111 , node_hyperdata = required "hyperdata"
112 -- , node_titleAbstract = optional "title_abstract"
117 nodeTable' :: Table (Maybe (Column PGInt4)
122 ,Maybe (Column PGTimestamptz)
130 ,(Column PGTimestamptz)
134 nodeTable' = Table "nodes" (PP.p7 ( optional "id"
135 , required "typename"
137 , required "parent_id"
140 , required "hyperdata"
145 queryNodeTable :: Query NodeRead
146 queryNodeTable = queryTable nodeTable
149 selectNode :: Column PGInt4 -> Query NodeRead
150 selectNode id = proc () -> do
151 row <- queryNodeTable -< ()
152 restrict -< node_id row .== id
155 runGetNodes :: Connection -> Query NodeRead -> IO [Node Value]
156 runGetNodes = runQuery
158 -- | order by publication date
159 -- Favorites (Bool), node_ngrams
160 selectNodesWith :: ParentId -> Maybe NodeType
161 -> Maybe Offset -> Maybe Limit -> Query NodeRead
162 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
163 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
164 limit' maybeLimit $ offset' maybeOffset $ orderBy (asc node_id) $ selectNodesWith' parentId maybeNodeType
166 selectNodesWith' :: ParentId -> Maybe NodeType -> Query NodeRead
167 selectNodesWith' parentId maybeNodeType = proc () -> do
168 node <- (proc () -> do
169 row@(Node _ typeId _ parentId' _ _ _) <- queryNodeTable -< ()
170 restrict -< parentId' .== (toNullable $ pgInt4 parentId)
172 let typeId' = maybe 0 nodeTypeId maybeNodeType
174 restrict -< if typeId' > 0
175 then typeId .== (pgInt4 (typeId' :: Int))
177 returnA -< row ) -< ()
181 deleteNode :: Connection -> Int -> IO Int
182 deleteNode conn n = fromIntegral <$> runDelete conn nodeTable
183 (\(Node n_id _ _ _ _ _ _) -> n_id .== pgInt4 n)
185 deleteNodes :: Connection -> [Int] -> IO Int
186 deleteNodes conn ns = fromIntegral <$> runDelete conn nodeTable
187 (\(Node n_id _ _ _ _ _ _) -> in_ ((map pgInt4 ns)) n_id)
190 getNodesWith :: Connection -> Int -> Maybe NodeType
191 -> Maybe Offset -> Maybe Limit -> IO [Node Value]
192 getNodesWith conn parentId nodeType maybeOffset maybeLimit =
193 runQuery conn $ selectNodesWith
194 parentId nodeType maybeOffset maybeLimit
198 getNodesWithParentId :: Connection -> Int
199 -> Maybe Text -> IO [Node Value]
200 getNodesWithParentId conn n _ = runQuery conn $ selectNodesWithParentID n
202 getNodesWithParentId' :: Connection -> Int
203 -> Maybe Text -> IO [Node Value]
204 getNodesWithParentId' conn n _ = runQuery conn $ selectNodesWithParentID n
207 selectNodesWithParentID :: Int -> Query NodeRead
208 selectNodesWithParentID n = proc () -> do
209 row@(Node _ _ _ parent_id _ _ _) <- queryNodeTable -< ()
212 parent_id .== (toNullable $ pgInt4 n)
218 selectNodesWithType :: Column PGInt4 -> Query NodeRead
219 selectNodesWithType type_id = proc () -> do
220 row@(Node _ tn _ _ _ _ _) <- queryNodeTable -< ()
221 restrict -< tn .== type_id
225 getNode :: Connection -> Int -> IO (Node Value)
227 fromMaybe (error $ "Node does node exist: " <> show id) . headMay <$> runQuery conn (limit 1 $ selectNode (pgInt4 id))
230 getNodesWithType :: Connection -> Column PGInt4 -> IO [Node HyperdataDocument]
231 getNodesWithType conn type_id = do
232 runQuery conn $ selectNodesWithType type_id
238 ------------------------------------------------------------------------
240 ------------------------------------------------------------------------
241 type NodeWrite' = NodePoly (Maybe Int) Int Int (ParentId) Text (Maybe UTCTime) ByteString
243 --node :: UserId -> ParentId -> NodeType -> Text -> Value -> NodeWrite'
244 node :: UserId -> ParentId -> NodeType -> Text -> Value -> NodeWrite'
245 node userId parentId nodeType name nodeData = Node Nothing typeId userId parentId name Nothing byteData
247 typeId = nodeTypeId nodeType
248 byteData = DB.pack $ DBL.unpack $ encode nodeData
252 node2write :: (Functor f2, Functor f1) =>
254 -> NodePoly (f1 Int) Int Int parentId Text (f2 UTCTime) ByteString
255 -> (f1 (Column PGInt4), Column PGInt4, Column PGInt4,
256 Column PGInt4, Column PGText, f2 (Column PGTimestamptz),
258 node2write pid (Node id tn ud _ nm dt hp) = ((pgInt4 <$> id)
268 mkNode :: Connection -> ParentId -> [NodeWrite'] -> IO Int64
269 mkNode conn pid ns = runInsertMany conn nodeTable' $ map (node2write pid) ns
271 mkNodeR :: Connection -> ParentId -> [NodeWrite'] -> IO [Int]
272 mkNodeR conn pid ns = runInsertManyReturning conn nodeTable' (map (node2write pid) ns) (\(i,_,_,_,_,_,_) -> i)
275 ------------------------------------------------------------------------
276 -- TODO Hierachy of Nodes
277 -- post and get same types Node' and update if changes
279 {- TODO semantic to achieve
280 post c uid pid [ Node' Corpus "name" "{}" []
281 , Node' Folder "name" "{}" [Node' Corpus "test 2" "" [ Node' Document "title" "metaData" []
282 , Node' Document "title" "jsonData" []
287 ------------------------------------------------------------------------
290 -- currently this function remove the child relation
291 -- needs a Temporary type between Node' and NodeWriteT
292 node2table :: UserId -> ParentId -> Node' -> [NodeWriteT]
293 node2table uid pid (Node' nt txt v []) = [( Nothing, (pgInt4$ nodeTypeId nt), (pgInt4 uid), (pgInt4 pid)
294 , pgStrictText txt, Nothing, pgStrictJSONB $ DB.pack $ DBL.unpack $ encode v)]
295 node2table _ _ (Node' _ _ _ _) = panic $ pack "node2table: should not happen, Tree insert not implemented yet"
298 data Node' = Node' { _n_type :: NodeType
301 , _n_children :: [Node']
305 type NodeWriteT = ( Maybe (Column PGInt4)
306 , Column PGInt4, Column PGInt4
307 , Column PGInt4, Column PGText
308 , Maybe (Column PGTimestamptz)
313 mkNode' :: Connection -> [NodeWriteT] -> IO Int64
314 mkNode' conn ns = runInsertMany conn nodeTable' ns
316 mkNodeR' :: Connection -> [NodeWriteT] -> IO [Int]
317 mkNodeR' conn ns = runInsertManyReturning conn nodeTable' ns (\(i,_,_,_,_,_,_) -> i)
320 postNode :: Connection -> UserId -> ParentId -> Node' -> IO [Int]
321 postNode c uid pid (Node' nt txt v []) = mkNodeR' c (node2table uid pid (Node' nt txt v []))
323 postNode c uid pid (Node' NodeCorpus txt v ns) = do
324 [pid'] <- postNode c uid pid (Node' NodeCorpus txt v [])
325 pids <- mkNodeR' c $ concat $ map (\n -> childWith uid pid' n) ns
328 postNode c uid pid (Node' Annuaire txt v ns) = do
329 [pid'] <- postNode c uid pid (Node' Annuaire txt v [])
330 pids <- mkNodeR' c $ concat $ map (\n -> childWith uid pid' n) ns
332 postNode _ _ _ (Node' _ _ _ _) = panic $ pack "postNode for this type not implemented yet"
335 childWith :: UserId -> ParentId -> Node' -> [NodeWriteT]
336 childWith uId pId (Node' Document txt v []) = node2table uId pId (Node' Document txt v [])
337 childWith uId pId (Node' UserPage txt v []) = node2table uId pId (Node' UserPage txt v [])
338 childWith _ _ (Node' _ _ _ _) = panic $ pack "This NodeType can not be a child"