Safe modelling of relational data in Haskell

前端 未结 5 1797
醉梦人生
醉梦人生 2021-01-30 13:25

I find it very common to want to model relational data in my functional programs. For example, when developing a web-site I may want to have the following data structure to stor

5条回答
  •  攒了一身酷
    2021-01-30 14:12

    I've been asked to write an answer using Opaleye. In fact there's not an awful lot to say, as the Opaleye code is fairly standard once you have a database schema. Anyway, here it is, assuming there is a user_table with columns user_id, name and birthdate, and a message_table with columns user_id, time_stamp and content.

    This sort of design is explained in more detail in the Opaleye Basic Tutorial.

    {-# LANGUAGE TemplateHaskell #-}
    {-# LANGUAGE FlexibleInstances #-}
    {-# LANGUAGE MultiParamTypeClasses #-}
    {-# LANGUAGE Arrows #-}
    
    import Opaleye
    import Data.Profunctor.Product (p2, p3)
    import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
    import Control.Arrow (returnA)
    
    data UserId a = UserId { unUserId :: a }
    $(makeAdaptorAndInstance "pUserId" ''UserId)
    
    data User' a b c = User { userId    :: a
                            , name      :: b
                            , birthDate :: c }
    $(makeAdaptorAndInstance "pUser" ''User')
    
    type User = User' (UserId (Column PGInt4))
                      (Column PGText)
                      (Column PGDate)
    
    data Message' a b c = Message { user      :: a
                                  , timestamp :: b
                                  , content   :: c }
    $(makeAdaptorAndInstance "pMessage" ''Message')
    
    type Message = Message' (UserId (Column PGInt4))
                            (Column PGDate)
                            (Column PGText)
    
    
    userTable :: Table User User
    userTable = Table "user_table" (pUser User
      { userId    = pUserId (UserId (required "user_id"))
      , name      = required "name"
      , birthDate = required "birthdate" })
    
    messageTable :: Table Message Message
    messageTable = Table "message_table" (pMessage Message
      { user      = pUserId (UserId (required "user_id"))
      , timestamp = required "timestamp"
      , content   = required "content" })
    

    An example query which joins the user table to the message table on the user_id field:

    usersJoinMessages :: Query (User, Message)
    usersJoinMessages = proc () -> do
      aUser    <- queryTable userTable    -< ()
      aMessage <- queryTable messageTable -< ()
    
      restrict -< unUserId (userId aUser) .== unUserId (user aMessage)
    
      returnA -< (aUser, aMessage)
    

提交回复
热议问题