karamaan / karamaan-opaleye

Other
11 stars 7 forks source link

How to handle serial columns in inserts? #47

Closed bergmark closed 10 years ago

bergmark commented 10 years ago

I currently do an insert like this:

postTable :: Table (Wire Int, Wire Text, Wire UTCTime, Wire Post.Title, Wire Text)
postTable = Table "posts" (Wire "id", Wire "author", Wire "created_time", Wire "title", Wire "content")

insertPost pg = runInsertConnDef pg postTable q
  where
    q :: Expr ( Maybe (Wire Int)
              , Maybe (Wire User.Name)
              , Maybe (Wire UTCTime)
              , Maybe (Wire Post.Title)
              , Maybe (Wire Text)
              )
    q = (,,,,)
     <$> pure (Nothing :: Maybe (Wire Int))
     <*> (Just <$> constant (Post.author      post))
     <*> (Just <$> constant (Post.createdTime post))
     <*> (Just <$> constant (Post.title       post))
     <*> (Just <$> constant (Post.content     post))

As far as I can tell, if I want to use MakeExpr instead i need to define a new Table just for inserts:

postTableInsert :: Table (Wire User.Name, Wire UTCTime, Wire Post.Title, Wire Text) 
postTableInsert = Table "posts" (Wire "author", Wire "created_time", Wire "title", Wire "content")

insertPost' pg = runInsertConnDef pg postTableInsert q'
  where
    q' :: Expr ( Maybe (Wire User.Name)
               , Maybe (Wire UTCTime)
               , Maybe (Wire Post.Title)
               , Maybe (Wire Text)
               )
    q' = ME.makeJustExpr
           ( Post.author post
           , Post.createdTime post
           , Post.title post
           , Post.content post
           )

Is there a recommended way to deal with this?

tomjaguarpaw commented 10 years ago

How about makeMaybeExpr? (You then have to put in all the Justs by hand.)

q' = ME.makeMaybeExpr
       ( Nothing
       , Just (Post.author post)
       , Just (Post.createdTime post)
       , Just (Post.title post)
       , Just (Post.content post)
       )

(Untested) It's not a lot better than your applicative version, but it is a bit.

tomjaguarpaw commented 10 years ago

It might also be worth trying

q' = fmap f $ ME.makeJustExpr
       ( Post.author post
       , Post.createdTime post
       , Post.title post
       , Post.content post
       )
    where f (a,b,c,d) = (Nothing, a, b, c, d)
bergmark commented 10 years ago

Thanks! These both work. I think I tried it before but must have made some silly mistake.

I'm toying with the thought of doing something persistent like, there the PK of a table is separate from generated data type, the QQ for a model/table generates something like

newtype PostId = PostId Int
data Post = Post
  { author      :: User.Name
  , createdTime :: UTCTime
  , title       :: Title
  , content     :: Text
  }

Then you have insertPost :: Post -> m () without the id, and someSelect :: m [(PostId, Post)]. Usually you don't care about the Id when working with the model.

An alternative would be to use a data type representation of the table, and then have insertPost function take unit as the id field.

What I usually end up doing in HDBC is to just inline the type into the signature, insertPost :: User.Name -> UTCTime -> Title -> Text -> m ().

tomjaguarpaw commented 10 years ago

I think what you mean by "have insertPost function take unit as the id field" is

data Post a = Post
  { id          :: a
  , author      :: User.Name
  , createdTime :: UTCTime
  , title       :: Title
  , content     :: Text
  }

and then use Post PostId or Post () depending on the situation. I like this approach personally. The best way to get all the convenience functions and types to play nicely together is still an open question though.