2 Module : Gargantext.Database.Schema.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 OverloadedStrings #-}
21 {-# LANGUAGE MultiParamTypeClasses #-}
22 {-# LANGUAGE NoImplicitPrelude #-}
23 {-# LANGUAGE RankNTypes #-}
24 {-# LANGUAGE TemplateHaskell #-}
26 module Gargantext.Database.Schema.Node where
28 import Control.Arrow (returnA)
29 import Control.Lens (Prism', set, view, (#), (^?))
30 import Control.Lens.TH (makeLensesWith, abbreviatedFields)
31 import Control.Monad.Error.Class (MonadError(..))
33 import Data.Maybe (Maybe(..), fromMaybe)
34 import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
35 import Data.Text (Text, pack)
36 import Database.PostgreSQL.Simple.FromField (FromField, fromField)
37 import GHC.Int (Int64)
38 import Gargantext.Core (Lang(..))
39 import Gargantext.Core.Types
40 import Gargantext.Core.Types.Individu (Username)
41 import Gargantext.Database.Config (nodeTypeId)
42 import Gargantext.Database.Queries.Filter (limit', offset')
43 import Gargantext.Database.Types.Node (NodeType, defaultCorpus, Hyperdata)
44 import Gargantext.Database.Utils
45 import Gargantext.Prelude hiding (sum, head)
46 import Opaleye hiding (FromField)
47 import Opaleye.Internal.QueryArr (Query)
48 import Prelude hiding (null, id, map, sum)
50 ------------------------------------------------------------------------
52 data NodeError = NoListFound
65 class HasNodeError e where
66 _NodeError :: Prism' e NodeError
68 nodeError :: (MonadError e m, HasNodeError e) => NodeError -> m a
69 nodeError ne = throwError $ _NodeError # ne
71 catchNodeError :: (MonadError e m, HasNodeError e) => m a -> (NodeError -> m a) -> m a
72 catchNodeError f g = catchError f (\e -> maybe (throwError e) g (e ^? _NodeError))
74 ------------------------------------------------------------------------
75 instance FromField HyperdataAny where
76 fromField = fromField'
78 instance FromField HyperdataCorpus
80 fromField = fromField'
82 instance FromField HyperdataDocument
84 fromField = fromField'
86 instance FromField HyperdataDocumentV3
88 fromField = fromField'
90 instance FromField HyperdataUser
92 fromField = fromField'
94 instance FromField HyperdataList
96 fromField = fromField'
98 instance FromField HyperdataListModel
100 fromField = fromField'
102 instance FromField HyperdataGraph
104 fromField = fromField'
106 instance FromField HyperdataAnnuaire
108 fromField = fromField'
110 instance FromField (NodeId, Text)
112 fromField = fromField'
113 ------------------------------------------------------------------------
114 instance QueryRunnerColumnDefault PGJsonb HyperdataAny
116 queryRunnerColumnDefault = fieldQueryRunnerColumn
118 instance QueryRunnerColumnDefault PGJsonb HyperdataDocument
120 queryRunnerColumnDefault = fieldQueryRunnerColumn
122 instance QueryRunnerColumnDefault PGJsonb HyperdataDocumentV3
124 queryRunnerColumnDefault = fieldQueryRunnerColumn
126 instance QueryRunnerColumnDefault PGJsonb HyperdataCorpus
128 queryRunnerColumnDefault = fieldQueryRunnerColumn
130 instance QueryRunnerColumnDefault PGJsonb HyperdataUser
132 queryRunnerColumnDefault = fieldQueryRunnerColumn
134 instance QueryRunnerColumnDefault PGJsonb HyperdataList
136 queryRunnerColumnDefault = fieldQueryRunnerColumn
138 instance QueryRunnerColumnDefault PGJsonb HyperdataListModel
140 queryRunnerColumnDefault = fieldQueryRunnerColumn
142 instance QueryRunnerColumnDefault PGJsonb HyperdataGraph
144 queryRunnerColumnDefault = fieldQueryRunnerColumn
146 instance QueryRunnerColumnDefault PGJsonb HyperdataAnnuaire
148 queryRunnerColumnDefault = fieldQueryRunnerColumn
150 instance QueryRunnerColumnDefault PGTSVector (Maybe TSVector)
152 queryRunnerColumnDefault = fieldQueryRunnerColumn
154 instance QueryRunnerColumnDefault PGInt4 (Maybe NodeId)
156 queryRunnerColumnDefault = fieldQueryRunnerColumn
158 instance QueryRunnerColumnDefault PGInt4 NodeId
160 queryRunnerColumnDefault = fieldQueryRunnerColumn
162 instance QueryRunnerColumnDefault (Nullable PGInt4) NodeId
164 queryRunnerColumnDefault = fieldQueryRunnerColumn
167 ------------------------------------------------------------------------
169 -- TODO Classe HasDefault where
170 -- default NodeType = Hyperdata
171 ------------------------------------------------------------------------
172 $(makeAdaptorAndInstance "pNode" ''NodePoly)
173 $(makeLensesWith abbreviatedFields ''NodePoly)
175 $(makeAdaptorAndInstance "pNodeSearch" ''NodePolySearch)
176 $(makeLensesWith abbreviatedFields ''NodePolySearch)
178 type NodeWrite = NodePoly (Maybe (Column PGInt4) )
181 (Maybe (Column PGInt4) )
183 (Maybe (Column PGTimestamptz))
186 type NodeRead = NodePoly (Column PGInt4 )
191 (Column PGTimestamptz )
194 type NodeReadNull = NodePoly (Column (Nullable PGInt4))
195 (Column (Nullable PGInt4))
196 (Column (Nullable PGInt4))
197 (Column (Nullable PGInt4))
198 (Column (Nullable PGText))
199 (Column (Nullable PGTimestamptz))
200 (Column (Nullable PGJsonb))
202 nodeTable :: Table NodeWrite NodeRead
203 nodeTable = Table "nodes" (pNode Node { _node_id = optional "id"
204 , _node_typename = required "typename"
205 , _node_userId = required "user_id"
207 , _node_parentId = optional "parent_id"
208 , _node_name = required "name"
209 , _node_date = optional "date"
211 , _node_hyperdata = required "hyperdata"
215 queryNodeTable :: Query NodeRead
216 queryNodeTable = queryTable nodeTable
218 ------------------------------------------------------------------------
219 -- | Node(Read|Write)Search is slower than Node(Write|Read) use it
220 -- for full text search only
221 type NodeSearchWrite =
223 (Maybe (Column PGInt4) )
226 (Column (Nullable PGInt4) )
228 (Maybe (Column PGTimestamptz))
230 (Maybe (Column PGTSVector) )
232 type NodeSearchRead =
237 (Column (Nullable PGInt4 ))
239 (Column PGTimestamptz )
243 type NodeSearchReadNull =
245 (Column (Nullable PGInt4) )
246 (Column (Nullable PGInt4) )
247 (Column (Nullable PGInt4) )
248 (Column (Nullable PGInt4) )
249 (Column (Nullable PGText) )
250 (Column (Nullable PGTimestamptz))
251 (Column (Nullable PGJsonb) )
252 (Column (Nullable PGTSVector) )
255 nodeTableSearch :: Table NodeSearchWrite NodeSearchRead
256 nodeTableSearch = Table "nodes" (pNodeSearch NodeSearch { _ns_id = optional "id"
257 , _ns_typename = required "typename"
258 , _ns_userId = required "user_id"
260 , _ns_parentId = required "parent_id"
261 , _ns_name = required "name"
262 , _ns_date = optional "date"
264 , _ns_hyperdata = required "hyperdata"
265 , _ns_search = optional "search"
270 queryNodeSearchTable :: Query NodeSearchRead
271 queryNodeSearchTable = queryTable nodeTableSearch
273 selectNode :: Column PGInt4 -> Query NodeRead
274 selectNode id = proc () -> do
275 row <- queryNodeTable -< ()
276 restrict -< _node_id row .== id
281 runGetNodes :: Query NodeRead -> Cmd err [NodeAny]
282 runGetNodes = runOpaQuery
284 ------------------------------------------------------------------------
285 ------------------------------------------------------------------------
287 -- | order by publication date
288 -- Favorites (Bool), node_ngrams
289 selectNodesWith :: ParentId -> Maybe NodeType
290 -> Maybe Offset -> Maybe Limit -> Query NodeRead
291 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
292 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
293 limit' maybeLimit $ offset' maybeOffset
294 $ orderBy (asc _node_id)
295 $ selectNodesWith' parentId maybeNodeType
297 selectNodesWith' :: ParentId -> Maybe NodeType -> Query NodeRead
298 selectNodesWith' parentId maybeNodeType = proc () -> do
299 node <- (proc () -> do
300 row@(Node _ typeId _ parentId' _ _ _) <- queryNodeTable -< ()
301 restrict -< parentId' .== (pgNodeId parentId)
303 let typeId' = maybe 0 nodeTypeId maybeNodeType
305 restrict -< if typeId' > 0
306 then typeId .== (pgInt4 (typeId' :: Int))
308 returnA -< row ) -< ()
311 deleteNode :: NodeId -> Cmd err Int
312 deleteNode n = mkCmd $ \conn ->
313 fromIntegral <$> runDelete conn nodeTable
314 (\(Node n_id _ _ _ _ _ _) -> n_id .== pgNodeId n)
316 deleteNodes :: [NodeId] -> Cmd err Int
317 deleteNodes ns = mkCmd $ \conn ->
318 fromIntegral <$> runDelete conn nodeTable
319 (\(Node n_id _ _ _ _ _ _) -> in_ ((map pgNodeId ns)) n_id)
321 -- TODO: NodeType should match with `a'
322 getNodesWith :: JSONB a => NodeId -> proxy a -> Maybe NodeType
323 -> Maybe Offset -> Maybe Limit -> Cmd err [Node a]
324 getNodesWith parentId _ nodeType maybeOffset maybeLimit =
325 runOpaQuery $ selectNodesWith parentId nodeType maybeOffset maybeLimit
327 -- TODO: Why is the second parameter ignored?
328 -- TODO: Why not use getNodesWith?
329 getNodesWithParentId :: NodeId -> Maybe Text -> Cmd err [NodeAny]
330 getNodesWithParentId n _ = runOpaQuery $ selectNodesWithParentID n
332 ------------------------------------------------------------------------
333 getDocumentsV3WithParentId :: NodeId -> Cmd err [Node HyperdataDocumentV3]
334 getDocumentsV3WithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeDocument)
336 -- TODO: merge with getDocumentsWithParentId by having a class IsHyperdataDocument
337 getDocumentsWithParentId :: NodeId -> Cmd err [Node HyperdataDocument]
338 getDocumentsWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeDocument)
340 getListsWithParentId :: NodeId -> Cmd err [Node HyperdataList]
341 getListsWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeList)
343 getListsModelWithParentId :: NodeId -> Cmd err [Node HyperdataListModel]
344 getListsModelWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeListModel)
346 getCorporaWithParentId :: NodeId -> Cmd err [Node HyperdataCorpus]
347 getCorporaWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeCorpus)
349 ------------------------------------------------------------------------
350 selectNodesWithParentID :: NodeId -> Query NodeRead
351 selectNodesWithParentID n = proc () -> do
352 row@(Node _ _ _ parent_id _ _ _) <- queryNodeTable -< ()
353 restrict -< parent_id .== (pgNodeId n)
356 selectNodesWithType :: Column PGInt4 -> Query NodeRead
357 selectNodesWithType type_id = proc () -> do
358 row@(Node _ tn _ _ _ _ _) <- queryNodeTable -< ()
359 restrict -< tn .== type_id
362 type JSONB = QueryRunnerColumnDefault PGJsonb
364 getNode :: JSONB a => NodeId -> proxy a -> Cmd err (Node a)
366 fromMaybe (error $ "Node does node exist: " <> show nId) . headMay
367 <$> runOpaQuery (limit 1 $ selectNode (pgNodeId nId))
369 getNode' :: NodeId -> Cmd err (Node Value)
370 getNode' nId = fromMaybe (error $ "Node does node exist: " <> show nId) . headMay
371 <$> runOpaQuery (limit 1 $ selectNode (pgNodeId nId))
374 getNodesWithType :: Column PGInt4 -> Cmd err [Node HyperdataDocument]
375 getNodesWithType = runOpaQuery . selectNodesWithType
377 ------------------------------------------------------------------------
378 ------------------------------------------------------------------------
379 defaultUser :: HyperdataUser
380 defaultUser = HyperdataUser (Just $ (pack . show) EN)
382 nodeUserW :: Maybe Name -> Maybe HyperdataUser -> UserId -> NodeWrite
383 nodeUserW maybeName maybeHyperdata = node NodeUser name user Nothing
385 name = maybe "User" identity maybeName
386 user = maybe defaultUser identity maybeHyperdata
387 ------------------------------------------------------------------------
388 defaultFolder :: HyperdataFolder
389 defaultFolder = HyperdataFolder (Just "Markdown Description")
391 nodeFolderW :: Maybe Name -> Maybe HyperdataFolder -> ParentId -> UserId -> NodeWrite
392 nodeFolderW maybeName maybeFolder pid = node NodeFolder name folder (Just pid)
394 name = maybe "Folder" identity maybeName
395 folder = maybe defaultFolder identity maybeFolder
396 ------------------------------------------------------------------------
397 nodeCorpusW :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> NodeWrite
398 nodeCorpusW maybeName maybeCorpus pId = node NodeCorpus name corpus (Just pId)
400 name = maybe "Corpus" identity maybeName
401 corpus = maybe defaultCorpus identity maybeCorpus
402 --------------------------
403 defaultDocument :: HyperdataDocument
404 defaultDocument = hyperdataDocument
406 nodeDocumentW :: Maybe Name -> Maybe HyperdataDocument -> CorpusId -> UserId -> NodeWrite
407 nodeDocumentW maybeName maybeDocument cId = node NodeDocument name doc (Just cId)
409 name = maybe "Document" identity maybeName
410 doc = maybe defaultDocument identity maybeDocument
411 ------------------------------------------------------------------------
412 defaultAnnuaire :: HyperdataAnnuaire
413 defaultAnnuaire = HyperdataAnnuaire (Just "Title") (Just "Description")
415 nodeAnnuaireW :: Maybe Name -> Maybe HyperdataAnnuaire -> ParentId -> UserId -> NodeWrite
416 nodeAnnuaireW maybeName maybeAnnuaire pId = node NodeAnnuaire name annuaire (Just pId)
418 name = maybe "Annuaire" identity maybeName
419 annuaire = maybe defaultAnnuaire identity maybeAnnuaire
421 ------------------------------------------------------------------------
422 arbitraryList :: HyperdataList
423 arbitraryList = HyperdataList (Just "Preferences")
425 nodeListW :: Maybe Name -> Maybe HyperdataList -> ParentId -> UserId -> NodeWrite
426 nodeListW maybeName maybeList pId = node NodeList name list (Just pId)
428 name = maybe "Listes" identity maybeName
429 list = maybe arbitraryList identity maybeList
433 arbitraryListModel :: HyperdataListModel
434 arbitraryListModel = HyperdataListModel (400,500) "data/models/test.model" (Just 0.83)
436 mkListModelNode :: HasNodeError err => ParentId -> UserId -> Cmd err [NodeId]
437 mkListModelNode p u = insertNodesR [nodeListModelW Nothing Nothing p u]
439 nodeListModelW :: Maybe Name -> Maybe HyperdataListModel -> ParentId -> UserId -> NodeWrite
440 nodeListModelW maybeName maybeListModel pId = node NodeListModel name list (Just pId)
442 name = maybe "List Model" identity maybeName
443 list = maybe arbitraryListModel identity maybeListModel
445 ------------------------------------------------------------------------
446 arbitraryGraph :: HyperdataGraph
447 arbitraryGraph = HyperdataGraph (Just "Preferences")
449 nodeGraphW :: Maybe Name -> Maybe HyperdataGraph -> ParentId -> UserId -> NodeWrite
450 nodeGraphW maybeName maybeGraph pId = node NodeGraph name graph (Just pId)
452 name = maybe "Graph" identity maybeName
453 graph = maybe arbitraryGraph identity maybeGraph
455 ------------------------------------------------------------------------
457 arbitraryDashboard :: HyperdataDashboard
458 arbitraryDashboard = HyperdataDashboard (Just "Preferences")
460 nodeDashboardW :: Maybe Name -> Maybe HyperdataDashboard -> ParentId -> UserId -> NodeWrite
461 nodeDashboardW maybeName maybeDashboard pId = node NodeDashboard name dashboard (Just pId)
463 name = maybe "Dashboard" identity maybeName
464 dashboard = maybe arbitraryDashboard identity maybeDashboard
466 ------------------------------------------------------------------------
467 node :: (ToJSON a, Hyperdata a) => NodeType -> Name -> a -> Maybe ParentId -> UserId -> NodeWrite
468 node nodeType name hyperData parentId userId = Node Nothing (pgInt4 typeId) (pgInt4 userId) (pgNodeId <$> parentId) (pgStrictText name) Nothing (pgJSONB $ cs $ encode hyperData)
470 typeId = nodeTypeId nodeType
472 -------------------------------
473 insertNodes :: [NodeWrite] -> Cmd err Int64
474 insertNodes ns = mkCmd $ \conn -> runInsertMany conn nodeTable ns
476 insertNodesR :: [NodeWrite] -> Cmd err [NodeId]
477 insertNodesR ns = mkCmd $ \conn ->
478 runInsert_ conn (Insert nodeTable ns (rReturning (\(Node i _ _ _ _ _ _) -> i)) Nothing)
480 insertNodesWithParent :: Maybe ParentId -> [NodeWrite] -> Cmd err Int64
481 insertNodesWithParent pid ns = insertNodes (set node_parentId (pgNodeId <$> pid) <$> ns)
483 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite] -> Cmd err [NodeId]
484 insertNodesWithParentR pid ns = insertNodesR (set node_parentId (pgNodeId <$> pid) <$> ns)
485 ------------------------------------------------------------------------
486 -- TODO Hierachy of Nodes
487 -- post and get same types Node' and update if changes
489 {- TODO semantic to achieve
490 post c uid pid [ Node' NodeCorpus "name" "{}" []
491 , Node' NodeFolder "name" "{}" [Node' NodeCorpus "test 2" "" [ Node' NodeDocument "title" "metaData" []
492 , Node' NodeDocument "title" "jsonData" []
497 ------------------------------------------------------------------------
500 -- currently this function removes the child relation
501 -- needs a Temporary type between Node' and NodeWriteT
502 node2table :: UserId -> Maybe ParentId -> Node' -> NodeWrite
503 node2table uid pid (Node' nt txt v []) = Node Nothing (pgInt4 $ nodeTypeId nt) (pgInt4 uid) (fmap pgNodeId pid) (pgStrictText txt) Nothing (pgStrictJSONB $ cs $ encode v)
504 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
507 data Node' = Node' { _n_type :: NodeType
510 , _n_children :: [Node']
513 mkNode :: [NodeWrite] -> Cmd err Int64
514 mkNode ns = mkCmd $ \conn -> runInsertMany conn nodeTable ns
516 mkNodeR :: [NodeWrite] -> Cmd err [NodeId]
517 mkNodeR ns = mkCmd $ \conn -> runInsertManyReturning conn nodeTable ns (_node_id)
519 ------------------------------------------------------------------------
521 data NewNode = NewNode { _newNodeId :: NodeId
522 , _newNodeChildren :: [NodeId] }
524 postNode :: HasNodeError err => UserId -> Maybe ParentId -> Node' -> Cmd err NewNode
525 postNode uid pid (Node' nt txt v []) = do
526 pids <- mkNodeR [node2table uid pid (Node' nt txt v [])]
528 [pid'] -> pure $ NewNode pid' []
529 _ -> nodeError ManyParents
531 postNode uid pid (Node' NodeCorpus txt v ns) = do
532 NewNode pid' _ <- postNode uid pid (Node' NodeCorpus txt v [])
533 pids <- mkNodeR (concat $ map (\n -> [childWith uid pid' n]) ns)
534 pure $ NewNode pid' pids
536 postNode uid pid (Node' NodeAnnuaire txt v ns) = do
537 NewNode pid' _ <- postNode uid pid (Node' NodeAnnuaire txt v [])
538 pids <- mkNodeR (concat $ map (\n -> [childWith uid pid' n]) ns)
539 pure $ NewNode pid' pids
540 postNode _ _ (Node' _ _ _ _) = nodeError NotImplYet
543 childWith :: UserId -> ParentId -> Node' -> NodeWrite
544 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
545 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
546 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
551 -- | TODO mk all others nodes
552 mkNodeWithParent :: HasNodeError err => NodeType -> Maybe ParentId -> UserId -> Name -> Cmd err [NodeId]
553 mkNodeWithParent NodeUser (Just _) _ _ = nodeError UserNoParent
554 mkNodeWithParent NodeUser Nothing uId name =
555 insertNodesWithParentR Nothing [node NodeUser name hd Nothing uId]
557 hd = HyperdataUser . Just . pack $ show EN
558 mkNodeWithParent _ Nothing _ _ = nodeError HasParent
559 mkNodeWithParent _ _ _ _ = nodeError NotImplYet
562 mkRoot :: HasNodeError err => Username -> UserId -> Cmd err [RootId]
563 mkRoot uname uId = case uId > 0 of
564 False -> nodeError NegativeId
565 True -> mkNodeWithParent NodeUser Nothing uId uname
568 -- CorpusDocument is a corpus made from a set of documents
569 -- CorpusContact is a corpus made from a set of contacts (syn of Annuaire)
570 data CorpusType = CorpusDocument | CorpusContact
574 mk :: Maybe Name -> Maybe a -> ParentId -> UserId -> Cmd err [NodeId]
576 instance MkCorpus HyperdataCorpus
578 mk n h p u = insertNodesR [nodeCorpusW n h p u]
581 instance MkCorpus HyperdataAnnuaire
583 mk n h p u = insertNodesR [nodeAnnuaireW n h p u]
586 getOrMkList :: HasNodeError err => ParentId -> UserId -> Cmd err ListId
587 getOrMkList pId uId =
588 maybe (mkList' pId uId) (pure . view node_id) . headMay =<< getListsWithParentId pId
590 mkList' pId uId = maybe (nodeError MkNode) pure . headMay =<< mkList pId uId
592 -- | TODO remove defaultList
593 defaultList :: HasNodeError err => CorpusId -> Cmd err ListId
595 maybe (nodeError NoListFound) (pure . view node_id) . headMay =<< getListsWithParentId cId
597 mkList :: HasNodeError err => ParentId -> UserId -> Cmd err [NodeId]
598 mkList p u = insertNodesR [nodeListW Nothing Nothing p u]
600 mkGraph :: ParentId -> UserId -> Cmd err [GraphId]
601 mkGraph p u = insertNodesR [nodeGraphW Nothing Nothing p u]
603 mkDashboard :: ParentId -> UserId -> Cmd err [NodeId]
604 mkDashboard p u = insertNodesR [nodeDashboardW Nothing Nothing p u]
606 -- | Default CorpusId Master and ListId Master
608 pgNodeId :: NodeId -> Column PGInt4
609 pgNodeId = pgInt4 . id2int