Combining Persistent with RIO logging to dump a table

330 Views Asked by At

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.

1

There are 1 best solutions below

2
On BEST ANSWER

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.