haskellpersistentconduitrio

Combining Persistent with RIO logging to dump a table


I'm writing a toy example to learn Haskell database access with the Persistent library. To play around, I want to see what's in the DB (SQLite in Memory):

import qualified Database.Persist.Sql          as PSQL
import qualified Data.Conduit.List             as CL
import           Data.Conduit                   ( ($$) )
import           Control.Monad.IO.Class         (liftIO)

dumpTable :: Text -> IO ()
dumpTable tableName = PSQL.rawQuery "select * from " <> tableName [] $$ CL.mapM_ (liftIO . print)

(Taken from the School of Haskell)

Because I want to use the RIO libraries for my applications, the above does not work: I need to use one of the RIO logging functions instead of print, and the function must run in the RIO monad. Here is my attempt to do so:

{-# LANGUAGE NoImplicitPrelude          #-}
{-# LANGUAGE OverloadedStrings          #-}
[..]

import           RIO
import qualified Database.Persist.Sql          as PSQL
import           Data.Conduit                   ( ($$) )
import qualified Data.Conduit.List             as CL

dumpTable :: (HasLogFunc env) => Text -> RIO env ()
dumpTable tableName =
    let query = "select * from " <> tableName
    in  PSQL.rawQuery query [] $$ CL.mapM_ (logInfo . displayShow)

However, this code does not type check. I get the following error:

    • Could not deduce (PSQL.BackendCompatible PSQL.SqlBackend env)
        arising from a use of ‘PSQL.rawQuery’
      from the context: HasLogFunc env
        bound by the type signature for:
                   dumpTable :: forall env. HasLogFunc env => Text -> RIO env ()
        at src/Persistence/DbInspect.hs:13:1-51
    • In the first argument of ‘($$)’, namely ‘PSQL.rawQuery query []’
      In the expression:
        PSQL.rawQuery query [] $$ CL.mapM_ (logInfo . displayShow)
      In the expression:
        let query = "select * from " <> tableName
        in PSQL.rawQuery query [] $$ CL.mapM_ (logInfo . displayShow)
   |
16 |     in  PSQL.rawQuery query [] $$ CL.mapM_ (logInfo . displayShow)
   |         ^^^^^^^^^^^^^^^^^^^^^^

I do not understand what this error means. It would be great if someone could give me some hints on how to go ahead and analyse this error, thereby improving my understanding of the involved typeclasses and monads.


Solution

  • Firstly instead of

    dumpTable :: Text -> IO ()
    dumpTable tableName = PSQL.rawQuery "select * from <> tableName" [] $$ CL.mapM_ (liftIO . print)
    

    you probably want this

    dumpTable :: Text -> IO ()
    dumpTable tableName = PSQL.rawQuery ("select * from " <> tableName) [] $$ CL.mapM_ (liftIO . print)
    

    Now assuming this version, what you have done here is you have selected a concrete type IO for dumpTable which should not type check.

    The function should be written like this

    dumpTable
      :: (MonadResource m, MonadReader env m,
          BackendCompatible SqlBackend env) =>
         Text -> m ()
    dumpTable tableName = PSQL.rawQuery ("select * from " <> tableName)  [] $$ CL.mapM_ (liftIO . print)
    

    I don't know which specific example you might be referring to but a simple example for runQuery would look something like this

    main :: IO ()
    main = runSqlite ":memory:" $ do
        buildDb
        dumpTable
    
    buildDb
      :: ReaderT SqlBackend (NoLoggingT (ResourceT IO)) (Key Tutorial)
    buildDb = do
        runMigrationSilent migrateTables
        insert $ Tutorial "Basic Haskell" "https://fpcomplete.com/school/basic-haskell-1" True
        insert $ Tutorial "A monad tutorial" "https://fpcomplete.com/user/anne/monads" False
        insert $ Tutorial "Yesod usage" "https://fpcomplete.com/school/basic-yesod" True
        insert $ Tutorial "Putting the FUN in functors" "https://fpcomplete.com/user/anne/functors" False
        insert $ Tutorial "Basic Haskell" "https://fpcomplete/user/anne/basics" False
    
    
    dumpTable
      :: ReaderT SqlBackend (NoLoggingT (ResourceT IO)) ()
    dumpTable = rawQuery "select * from Tutorial" [] $$ CL.mapM_ (liftIO . print)
    

    Above example is from Dumping a table

    Without going into too much details, the way to satisfy these constraints ReaderT SqlBackend (NoLoggingT (ResourceT IO)) is by using each monad transformers' run functions. For ReaderT that would be runReaderT i.e. runReaderT configData $ monadReaderConstraiendFunction.

    Anyways, you might want to take a look at how Monad Transformers work before diving into this library. It won't take too long, and once you get the gist of it you'll be able to debug any further problems.

    Speaking of which, now lets take a look at this part of the error message

    • Could not deduce (PSQL.BackendCompatible PSQL.SqlBackend env)
        arising from a use of ‘PSQL.rawQuery’
      from the context: HasLogFunc env
    

    and your function type

    dumpTable :: (HasLogFunc env) => Text -> RIO env ()
    

    The monad env is constrained on HasLogFunc but the function rawQuery requires several other contexts to work in as we saw above.

    You can see that from rawQuery's function type (MonadResource m, MonadReader env m, BackendCompatible SqlBackend env).

    Basically we need to help GHC out by explicitly defining those in its type signature. I don't know how you're interacting with the RIO monad, but the most general case should look like this

    dumpTable :: (HasLogFunc env
                 , PSQL.BackendCompatible PSQL.SqlBackend env
                 , MonadResource (RIO env)
                 )
              => Text
              -> RIO env ()
    dumpTable tableName =
        let query = "select * from " <> tableName
        in  PSQL.rawQuery query [] $$ CL.mapM_ (logInfo . displayShow)
    

    Now this will type check.