Search code examples
haskellhaskell-persistent

Fetch a Persistent record given its integral key?


I'm trying to use Persistent with Servant, so I don't have the luxury of automatically parsing URL segments into Persistent keys. Instead, I've set up my routes to require an Int64, and I want to retrieve a record using that to perform primary key lookup.

Everything I've found points to using toSqlKey to convert an integer to a key, so I tried to write a very simple function that would do that for me:

runDB :: (MonadBaseControl IO m, MonadIO m) => (SqlPersistT (NoLoggingT (ResourceT m))) a -> m a
runDB actions = do
  filename <- liftIO $ getEnv "SQLITE_FILENAME"
  runSqlite (pack filename) actions

getRecordByKey :: Int64 -> IO (Maybe (Entity Record))
getRecordByKey recordId = runDB $ get (toSqlKey recordId)

Unfortunately, this didn't work; I got the following type error:

Couldn't match expected type ‘PersistEntityBackend
                                (Entity Record)’
            with actual type ‘SqlBackend’
In the second argument of ‘($)’, namely ‘get (toSqlKey recordId)’
In the expression: runDB $ get (toSqlKey recordId)
In an equation for ‘getRecordByKey’:
    getRecordByKey recordId = runDB $ get (toSqlKey recordId)

I sort of understand this error—I looked up the types for get and toSqlKey, and they include the relevant constraints:

get :: (MonadIO m, backend ~ PersistEntityBackend val, PersistEntity val) => Key val -> ReaderT backend m (Maybe val)
toSqlKey :: ToBackendKey SqlBackend record => Int64 -> Key record

If I understand correctly, backend and PersistEntityBackend val need to be the same type, but toSqlKey enforces the SqlBackend constraint, so the types mismatch. My intuition tells me that PersistentEntityBackend (Entity Record) should be SqlBackend, but evidently I'm wrong there. I don't know why or how, though.

Anyway, I don't know if I'm right or wrong in that analysis, but either way, I'm not sure how to fix this or what the proper way of doing this is. How can/should I be getting a record from my database given an integer?


Solution

  • this works for me (may depend on the versions of your packages ... sadly):

    {-# LANGUAGE FlexibleContexts #-}
    module Stackoverlflow where
    
    import Control.Monad.IO.Class (MonadIO, liftIO)
    import Control.Monad.Logger(NoLoggingT)
    import Control.Monad.Trans.Control (MonadBaseControl)
    import Control.Monad.Trans.Resource (ResourceT)
    import Data.Int (Int64)
    import Data.Text (pack)
    import Database.Persist.Class (ToBackendKey, get)
    import Database.Persist.Sql (SqlBackend, SqlPersistT, toSqlKey)
    import Database.Persist.Sqlite(runSqlite)
    import Database.Persist.Types (Entity)
    import System.Environment (getEnv)
    
    runDB :: (MonadBaseControl IO m, MonadIO m) =>
            (SqlPersistT (NoLoggingT (ResourceT m))) a -> m a
    runDB actions = do
      filename <- liftIO $ getEnv "SQLITE_FILENAME"
      runSqlite (pack filename) actions
    
    getRecordByKey :: (MonadIO m, ToBackendKey SqlBackend val, MonadBaseControl IO m) =>
                     Int64 -> m (Maybe val)
    getRecordByKey recordId = runDB $ get (toSqlKey recordId)
    

    as you can see I just added alot of types annotations (well GHC did after I removed the signatures and ask it to tell me ;))

    also note that I don't have your Record so you should easily be able to get rid of the ... val stuff!