persistent-typed-db icon indicating copy to clipboard operation
persistent-typed-db copied to clipboard

Provide an example implementation

Open dariooddenino opened this issue 8 years ago • 5 comments

Hi! I'm trying to use this library to have my Yesod app use both a postgres and a mysql databases. I'll call the dbs master and liv.

In Application.hs's makeFoundation function I have:

let mkFoundation appConnPool livConnPool = App {..}
    tempFoundation = mkFoundation (error "connPool forced in tempFoundation") (error "X")
    logFunc = messageLoggerSource tempFoundation appLogger

pool <- flip runLoggingT logFunc $ createPostgresqlPool
    (pgConnStr  $ appDatabaseConf appSettings)
    (pgPoolSize $ appDatabaseConf appSettings)

livpool <- flip runLoggingT logFunc $ createMySQLPool
    (myConnInfo $ livDatabaseConf appSettings)
    (myPoolSize $ livDatabaseConf appSettings)

runLoggingT (runSqlPool (runMigration migrateAll) pool) logFunc

return $ mkFoundation (specializePool pool) (specializePool livpool)

In Model.hs:

data MasterDB
type MasterQueryT = SqlPersistTFor MasterDB
type MasterQueryM = SqlPersistMFor MasterDB

data LivDB
type LivQueryT = SqlPersistTFor LivDB
type LivQueryM = SqlPersistMFor LivDB


share [mkPersist (mkSqlSettingsFor ''MasterDB), mkMigrate "migrateAll"]
    $(persistFileWith lowerCaseSettings "config/models")

share [mkPersist (mkSqlSettingsFor ''LivDB)]
    $(persistFileWith lowerCaseSettings "config/livmodels")

Then in Foundation.hs:

data App = App
    { appSettings    :: AppSettings
    , appStatic      :: Static -- ^ Settings for static file serving.
    , appConnPool    :: ConnectionPoolFor MasterDB -- ^ Database connection pool.
    , livConnPool    :: ConnectionPoolFor LivDB -- ^ Liv's database connection pool.
    , appHttpManager :: Manager
    , appLogger      :: Logger
    }

Now the issue is that the default runDB doesn't typecheck anymore and I wasn't able to fix it. I tried various combinations of generalizeQuery, generalizePool and so on, but I always end up with a Couldn't match type 'SqlBackend' with SqlFor MasterDB' error.

The idea would be to have runDB only run on the MasterDB and then a runLiv function that runs on the LivDB only.

This is what produced the least amount of errors so far:

fromMasterQuery :: ReaderT (SqlFor MasterDB) m a -> ReaderT SqlBackend m a
fromMasterQuery = generalizeQuery

instance YesodPersist App where
    type YesodPersistBackend App = SqlFor MasterDB
    runDB action = do
        conn <- getYesod
        runSQlPool (fromMasterQuery action) (generalizePool $ appConnPool conn)

Thanks a lot for any help! :)

dariooddenino avatar Oct 19 '17 16:10 dariooddenino

What is the type of runDB? I suspect that the type of runDB is somehow using the YesodPersistBackend App type family to choose what the parameter has to be.

Can you post the exact errors you receive?

parsonsmatt avatar Oct 19 '17 16:10 parsonsmatt

I have not access to the code right now, but the problem was with runDB and defaultGetDBRunner. They both expect a SqlBackend and not a SqlFor MasterDB.

class Monad (YesodDB site) => YesodPersist site where
    type YesodPersistBackend site
    runDB :: YesodDB site a -> HandlerT site IO a

defaultGetDBRunner :: (SQL.IsSqlBackend backend, YesodPersistBackend site ~ backend)
                   => (site -> Pool backend)
                   -> HandlerT site IO (DBRunner site, HandlerT site IO ())

dariooddenino avatar Oct 19 '17 18:10 dariooddenino

Here's my code for this:

-- How to run database actions.
instance YesodPersist App where
    type YesodPersistBackend App = SqlFor FbgMasterDb
    runDB action = do
        master <- getYesod
        runSqlPool (generalizeQuery action) (generalizePool $ appConnPool master)

Which... looks exactly like yours. Interesting.

parsonsmatt avatar Oct 19 '17 18:10 parsonsmatt

I think the issue is with:

instance YesodPersistRunner App where
    getDBRunner = defaultGetDBRunner appConnPool

which gives me this error:

  • Couldn't match type ‘SqlFor MasterDB’ with ‘SqlBackend’
        arising from a use of ‘defaultGetDBRunner’
    • In the expression: defaultGetDBRunner appConnPool
      In an equation for ‘getDBRunner’:
          getDBRunner = defaultGetDBRunner appConnPool
      In the instance declaration for ‘YesodPersistRunner App’

Trying to generalize the appConnPool didn't help. I ended up commenting those two lines and now it seems like I can make queries to the MasterDB correctly.

What I'm missing is a way to query the other db, as doing this in a standard yesod handler doesn't type check:

runLiv :: SqlPersistTFor LivDB (HandlerT App IO) a -> HandlerT App IO a
runLiv action = do
  conn <- getYesod
  runSqlPool (generalizeQuery action) (generalizePool $ livConnPool conn)

If I try to use this in an Esqueleto query, I get:

   • Couldn't match type ‘Database.Persist.Typed.SqlFor LivDB’
                     with ‘SqlBackend’
        arising from a use of ‘E.select’
    • In the second argument of ‘($)’, namely
        ‘E.select
         $ E.from
           $ \ o
               -> do { E.where_ (o E.^. Wp_postsId E.==. E.val 1284);
                       return o }’
      In a stmt of a 'do' block:
        posts <- runLiv
                 $ E.select
                   $ E.from
                     $ \ o
                         -> do { E.where_ (o E.^. Wp_postsId E.==. E.val 1284);
                                 return o }
      In the expression:
        do { posts <- runLiv $ E.select $ E.from $ \ o -> do { ... };
             let oname :: [Text]
                 oname = map (pack <$> wp_postsPost_status . E.entityVal) wps;
             oname }

As a last edit, it works fine for standard persistent queries, I guess I need to figure out how to make it work with esqueleto :)

Sorry for all the noise

dariooddenino avatar Oct 23 '17 09:10 dariooddenino

Ahh, I am going to be putting together a patch today for Esqueleto to work with the BackendCompatible class :) You can follow here for details.

parsonsmatt avatar Oct 23 '17 15:10 parsonsmatt