opaleye-trans-0.3.0: examples/v2/RoseTree.hs
{-# LANGUAGE Arrows #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Main where
import Control.Arrow
import Control.Monad (void)
import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
import qualified Database.PostgreSQL.Simple as PSQL
import Opaleye
import Opaleye.Trans
data Rose a
= Node a [Rose a]
| Leaf a
deriving (Show, Eq)
instance Functor Rose where
fmap f (Node x rs) = Node (f x) (map (fmap f) rs)
fmap f (Leaf x) = Leaf (f x)
data NodeP i b a = NodeP
{ nodeId :: i
, nodeBranchId :: b
, value :: a
} deriving (Show, Eq)
data NodeBranchP i n = NodeBranchP
{ nodeIdRef :: i
, nextBranchId :: n
} deriving (Show, Eq)
type WriteNode a = NodeP (Maybe (Column PGInt4)) (Column PGInt4) (Column a)
type ReadNode a = NodeP (Column PGInt4) (Column PGInt4) (Column a)
type NodeBranch = NodeBranchP (Column PGInt4) (Column PGInt4)
type NullableNodeBranch = NodeBranchP (Column (Nullable PGInt4)) (Column (Nullable PGInt4))
data BranchP i = BranchP
{ branchId :: i
} deriving (Show, Eq)
type WriteBranch = BranchP (Maybe (Column PGInt4))
type ReadBranch = BranchP (Column PGInt4)
data TreeP i r = TreeP
{ treeId :: i
, rootId :: r
} deriving (Show, Eq)
type WriteTree = TreeP (Maybe (Column PGInt4)) (Column PGInt4)
type ReadTree = TreeP (Column PGInt4) (Column PGInt4)
makeAdaptorAndInstance "pNode" ''NodeP
makeAdaptorAndInstance "pNodeBranch" ''NodeBranchP
makeAdaptorAndInstance "pBranch" ''BranchP
makeAdaptorAndInstance "pTree" ''TreeP
nodeTable :: Table (WriteNode a) (ReadNode a)
nodeTable = Table "node" $ pNode NodeP
{ nodeId = optional "id"
, nodeBranchId = required "branch_id"
, value = required "value"
}
nodeBranchTable :: Table NodeBranch NodeBranch
nodeBranchTable = Table "node_branch" $ pNodeBranch NodeBranchP
{ nodeIdRef = required "id"
, nextBranchId = required "next_branch_id"
}
branchTable :: Table WriteBranch ReadBranch
branchTable = Table "branch" $ pBranch (BranchP (optional "id"))
treeTable :: Table WriteTree ReadTree
treeTable = Table "rosetree" $ pTree TreeP
{ treeId = optional "id"
, rootId = required "root_id"
}
newTree :: Int -> Transaction (Maybe Int)
newTree rootId =
insertReturningFirst treeTable treeId (TreeP Nothing (pgInt4 rootId))
newBranch :: Transaction (Maybe Int)
newBranch = insertReturningFirst branchTable branchId (BranchP Nothing)
insertNode :: Int -> Maybe Int -> Int -> Transaction (Maybe Int)
insertNode bid (Just nbid) x = do
Just nodeId <- insertReturningFirst nodeTable nodeId
(NodeP Nothing (pgInt4 bid) (pgInt4 x))
insert nodeBranchTable (NodeBranchP (pgInt4 nodeId) (pgInt4 nbid))
return (Just nodeId)
insertNode bid Nothing x =
insertReturningFirst nodeTable nodeId
(NodeP Nothing (pgInt4 bid) (pgInt4 x))
insertTree :: MonadIO m => Rose Int -> OpaleyeT m Int
insertTree (Node x xs) = transaction $ do
Just bid <- newBranch
Just rootId <- insertNode 0 (Just bid) x
Just treeId <- newTree rootId
mapM_ (insertTree' bid) xs
return treeId
insertTree (Leaf x) = transaction $ do
Just rootId <- insertNode 0 Nothing x
Just treeId <- newTree rootId
return treeId
insertTree' :: Int -> Rose Int -> Transaction ()
insertTree' bid (Node x xs) = do
Just nbid <- newBranch
insertNode bid (Just nbid) x
mapM_ (insertTree' nbid) xs
insertTree' bid (Leaf x) =
void (insertNode bid Nothing x)
-- TODO Wrong order
selectTree :: Int -> Transaction (Rose Int)
selectTree treeId = do
Just rootId <- selectRootNode treeId
Just (NodeP _ _ x, NodeBranchP _ mbid) <- selectNode rootId
case mbid of
Just nbid -> do
xs <- selectBranch nbid
return (Node x xs)
Nothing -> return (Leaf x)
selectRootNode :: Int -> Transaction (Maybe Int)
selectRootNode tid = queryFirst rootNode
where
rootNode :: Query (Column PGInt4)
rootNode = proc () -> do
tree <- queryTable treeTable -< ()
restrict -< treeId tree .== pgInt4 tid
returnA -< rootId tree
nodeAndBranch :: Query (ReadNode PGInt4, NullableNodeBranch)
nodeAndBranch = leftJoin allNodes allNodeBranches (uncurry idsEqual)
where
allNodes = queryTable nodeTable
allNodeBranches = queryTable nodeBranchTable
idsEqual :: ReadNode PGInt4 -> NodeBranch -> Column PGBool
idsEqual (NodeP nid' _ _) (NodeBranchP nbid _) = nid' .== nbid
byId :: Query a -> (a -> Column PGInt4) -> Int -> Query a
byId q getId id' = proc () -> do
row <- q -< ()
restrict -< getId row .== pgInt4 id'
returnA -< row
selectNode :: Int -> Transaction (Maybe (NodeP Int Int Int, NodeBranchP (Maybe Int) (Maybe Int)))
selectNode nid = queryFirst nodeById
where
nodeById :: Query (ReadNode PGInt4, NullableNodeBranch)
nodeById = byId nodeAndBranch (nodeId . fst) nid
selectBranch :: Int -> Transaction [Rose Int]
selectBranch bid = do
nodes <- query nodeByBranchId
sequence (mkNode <$> nodes)
where
nodeByBranchId :: Query (ReadNode PGInt4, NullableNodeBranch)
nodeByBranchId = byId nodeAndBranch (nodeBranchId . fst) bid
mkNode
:: (NodeP Int Int Int, NodeBranchP (Maybe Int) (Maybe Int))
-> Transaction (Rose Int)
mkNode (NodeP _ _ x, NodeBranchP Nothing Nothing) = return (Leaf x)
mkNode (NodeP _ _ x, NodeBranchP _ (Just nbid)) = do
xs <- selectBranch nbid
return (Node x xs)
mkNode x = error $ "Bad branch select. Who knows?: " ++ show x
main :: IO ()
main = do
conn <- PSQL.connectPostgreSQL "dbname='rosetree' user='postgres'"
let tree :: Rose Int
tree =
Node 6
[ Node 7
[ Node 1425
[ Leaf 42
, Leaf 2354
, Leaf 4245]]
, Leaf 10
, Node 6
[ Leaf 12
, Leaf 14]]
tree' <- runOpaleyeT conn $ run . selectTree =<< insertTree tree
print tree
print tree'
print (tree == tree')