Alexis King
Alexis King

Reputation: 43852

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?

Upvotes: 0

Views: 616

Answers (2)

ondra
ondra

Reputation: 9331

And what about making Key record directly an instance of FromText/ToText and use the keys in the URL directly?

{-# LANGUAGE FlexibleContexts               #-}
{-# LANGUAGE UndecidableInstances               #-}
instance ToBackendKey SqlBackend record => FromText (Key record) where
  fromText k = toSqlKey <$> fromText k
instance ToBackendKey SqlBackend record => ToText (Key record) where
  toText = toText . fromSqlKey

Upvotes: 1

Random Dev
Random Dev

Reputation: 52280

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!

Upvotes: 2

Related Questions