Search code examples
haskellhaskell-persistent

Inner join in persistent or should I use esqueleto?


I have this fragment describing Notification and Notified entities:

Notification
  type          NotiType
  release       ReleaseId
  date          UTCTime
Notified
  aboutWhat     NotificationId
  unread        Bool
  user          UserId

Now I want to write this:

-- | Mark specified notification as already seen by specific user. Note that
-- we use 'ReleaseId' to select notification, so this may result in several
-- notifications marked as “read” if they happen to be about the same
-- release. This is generally what you want.

markAsRead
  :: ReleaseId         -- ^ Release in question
  -> UserId            -- ^ User who has seen mentioned release
  -> SqlPersistM ()
markAsRead release user = do
  ns <- selectKeysList [ NotificationRelease ==. release ] []
  updateWhere [ NotifiedAboutWhat <-. ns
              , NotifiedUnread    ==. True
              , NotifiedUser      ==. user ]
              [ NotifiedUnread    =.  False ]

This works, but extracting list of notifications as list and then using it to select things in another table… well that's not exactly right. Obviously I need a join here and then I will be able to update everything efficiently.

How to do it in pure persistent? Is it possible and is it a good idea in this case to stay with persistent for this sort of task? Should I use esqueleto instead? It looks like I'll need to learn different DSL to work with it, so I'm not sure whether to switch or not.

How to write markAsRead properly with persistent (if possible)?


Solution

  • As Greg mentioned, Esqueleto is the way to go. You can try reading its main module documentation.

    Currently Esqueleto doesn't support joins on UPDATEs. However, you can use subqueries to the same effect.

    Untested code to get you started:

    -- | Mark specified notification as already seen by specific user. Note that
    -- we use 'ReleaseId' to select notification, so this may result in several
    -- notifications marked as “read” if they happen to be about the same
    -- release. This is generally what you want.
    markAsRead
      :: ReleaseId         -- ^ Release in question
      -> UserId            -- ^ User who has seen mentioned release
      -> SqlPersistM ()
    markAsRead release user = 
      update $ \n -> do
      set n [ NotifiedUnread =. val False ]
      where_ $
        n ^. NotifiedUnread  ==. val True &&.
        n ^. NotifiedUser    ==. val user &&.
        n ^. NotifiedAboutWhat `in_` 
          (subList_select $
           from $ \t -> do
           where_ $ t ^. NotificationRelease ==. val release
           return $ t ^. NotificationId)