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
224 getNode' :: Connection -> Int -> IO (Node Value)
226 fromMaybe (error "TODO: 404") . headMay <$> runQuery c (limit 1 $ selectNode (pgInt4 id))
229 getNode :: Connection -> Int -> IO (Node Value)
231 fromMaybe (error "TODO: 404") . headMay <$> runQuery conn (limit 1 $ selectNode (pgInt4 id))
233 getNodesWithType :: Connection -> Column PGInt4 -> IO [Node HyperdataDocument]
234 getNodesWithType conn type_id = do
235 runQuery conn $ selectNodesWithType type_id
241 ------------------------------------------------------------------------
243 ------------------------------------------------------------------------
244 type NodeWrite' = NodePoly (Maybe Int) Int Int (ParentId) Text (Maybe UTCTime) ByteString
246 --node :: UserId -> ParentId -> NodeType -> Text -> Value -> NodeWrite'
247 node :: UserId -> ParentId -> NodeType -> Text -> Value -> NodeWrite'
248 node userId parentId nodeType name nodeData = Node Nothing typeId userId parentId name Nothing byteData
250 typeId = nodeTypeId nodeType
251 byteData = DB.pack $ DBL.unpack $ encode nodeData
255 node2write :: (Functor f2, Functor f1) =>
257 -> NodePoly (f1 Int) Int Int parentId Text (f2 UTCTime) ByteString
258 -> (f1 (Column PGInt4), Column PGInt4, Column PGInt4,
259 Column PGInt4, Column PGText, f2 (Column PGTimestamptz),
261 node2write pid (Node id tn ud _ nm dt hp) = ((pgInt4 <$> id)
271 mkNode :: Connection -> ParentId -> [NodeWrite'] -> IO Int64
272 mkNode conn pid ns = runInsertMany conn nodeTable' $ map (node2write pid) ns
274 mkNodeR :: Connection -> ParentId -> [NodeWrite'] -> IO [Int]
275 mkNodeR conn pid ns = runInsertManyReturning conn nodeTable' (map (node2write pid) ns) (\(i,_,_,_,_,_,_) -> i)
278 ------------------------------------------------------------------------
279 -- TODO Hierachy of Nodes
280 -- post and get same types Node' and update if changes
282 {- TODO semantic to achieve
283 post c uid pid [ Node' Corpus "name" "{}" []
284 , Node' Folder "name" "{}" [Node' Corpus "test 2" "" [ Node' Document "title" "metaData" []
285 , Node' Document "title" "jsonData" []
290 ------------------------------------------------------------------------
293 -- currently this function remove the child relation
294 -- needs a Temporary type between Node' and NodeWriteT
295 node2table :: UserId -> ParentId -> Node' -> [NodeWriteT]
296 node2table uid pid (Node' nt txt v []) = [( Nothing, (pgInt4$ nodeTypeId nt), (pgInt4 uid), (pgInt4 pid)
297 , pgStrictText txt, Nothing, pgStrictJSONB $ DB.pack $ DBL.unpack $ encode v)]
298 node2table _ _ (Node' _ _ _ _) = panic $ pack "node2table: should not happen, Tree insert not implemented yet"
301 data Node' = Node' { _n_type :: NodeType
304 , _n_children :: [Node']
308 type NodeWriteT = ( Maybe (Column PGInt4)
309 , Column PGInt4, Column PGInt4
310 , Column PGInt4, Column PGText
311 , Maybe (Column PGTimestamptz)
316 mkNode' :: Connection -> [NodeWriteT] -> IO Int64
317 mkNode' conn ns = runInsertMany conn nodeTable' ns
319 mkNodeR' :: Connection -> [NodeWriteT] -> IO [Int]
320 mkNodeR' conn ns = runInsertManyReturning conn nodeTable' ns (\(i,_,_,_,_,_,_) -> i)
323 postNode :: Connection -> UserId -> ParentId -> Node' -> IO [Int]
324 postNode c uid pid (Node' nt txt v []) = mkNodeR' c (node2table uid pid (Node' nt txt v []))
326 postNode c uid pid (Node' NodeCorpus txt v ns) = do
327 [pid'] <- postNode c uid pid (Node' NodeCorpus txt v [])
328 pids <- mkNodeR' c $ concat $ map (\n -> childWith uid pid' n) ns
331 postNode c uid pid (Node' Annuaire txt v ns) = do
332 [pid'] <- postNode c uid pid (Node' Annuaire txt v [])
333 pids <- mkNodeR' c $ concat $ map (\n -> childWith uid pid' n) ns
335 postNode _ _ _ (Node' _ _ _ _) = panic $ pack "postNode for this type not implemented yet"
338 childWith :: UserId -> ParentId -> Node' -> [NodeWriteT]
339 childWith uId pId (Node' Document txt v []) = node2table uId pId (Node' Document txt v [])
340 childWith uId pId (Node' UserPage txt v []) = node2table uId pId (Node' UserPage txt v [])
341 childWith _ _ (Node' _ _ _ _) = panic $ pack "This NodeType can not be a child"