gbwey/persistent-odbc

Yesod

Opened this issue · 6 comments

How can I easily use it with yesod through stack? (By the way, thank you so much for this repo, it was the only way I've found to use Persistent+Mysql+Windows)

gbwey commented

I just tried this and it looks like you would need to add at least these extra dependencies to stack.yaml and then run "stack build".

extra-deps:

  • persistent-odbc-0.2.0.1
  • HDBC-odbc-2.5.0.0
  • concurrent-extra-0.7.0.10

Let me know if you need more info.
Best,
Grant

How can I implement YesodPersist in Yesod?

I tryed the following which doesn't compile:

-- How to run database actions.
instance YesodPersist App where
  runDB = runResourceT . runNoLoggingT . withODBCConn Nothing "dsn=mysql_test" . runSqlConn

For instance, I've found the following code from the Yesod book:

{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE GADTs                      #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE QuasiQuotes                #-}
{-# LANGUAGE TemplateHaskell            #-}
{-# LANGUAGE TypeFamilies               #-}
import           Control.Monad.Logger    (runNoLoggingT)
import           Data.Text               (Text)
import           Data.Time
import           Database.Persist.Sqlite
import           Yesod

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Link
    title Text
    url Text
    added UTCTime
|]

data App = App ConnectionPool

mkYesod "App" [parseRoutes|
/         HomeR    GET
/add-link AddLinkR POST
|]

instance Yesod App

instance RenderMessage App FormMessage where
    renderMessage _ _ = defaultFormMessage

instance YesodPersist App where
    type YesodPersistBackend App = SqlBackend
    runDB db = do
        App pool <- getYesod
        runSqlPool db pool

getHomeR :: Handler Html
getHomeR = defaultLayout
    [whamlet|
        <form method=post action=@{AddLinkR}>
            <p>
                Add a new link to
                <input type=url name=url value=http://>
                titled
                <input type=text name=title>
                <input type=submit value="Add link">
        <h2>Existing links
        ^{existingLinks}
    |]

existingLinks :: Widget
existingLinks = do
    links <- handlerToWidget $ runDB $ selectList [] [LimitTo 5, Desc LinkAdded]
    [whamlet|
        <ul>
            $forall Entity _ link <- links
                <li>
                    <a href=#{linkUrl link}>#{linkTitle link}
    |]

postAddLinkR :: Handler ()
postAddLinkR = do
    url <- runInputPost $ ireq urlField "url"
    title <- runInputPost $ ireq textField "title"
    now <- liftIO getCurrentTime
    runDB $ insert $ Link title url now
    setMessage "Link added"
    redirect HomeR

main :: IO ()
main = runNoLoggingT $ withSqlitePool "links.db3" 10 $ \pool -> liftIO $ do
    runSqlPersistMPool (runMigration migrateAll) pool
    warp 3000 $ App pool
gbwey commented

stack new abcd yesod-simple [ie create project with yesod-simple template just to setup all yesod dependencies]

then add these dependencies to the abcd.cabal file
, persistent-odbc
, esqueleto

add these extra dependencies to stack.yaml
extra-deps:

  • persistent-odbc-0.2.0.1
  • HDBC-odbc-2.5.0.0
  • concurrent-extra-0.7.0.10

run: stack build

Create this file: I dont have MySql on my machine so I used sqlite3 instead but it is similar:
the key line is:
pool <- runStderrLoggingT $ createODBCPool (Just (Sqlite True)) "dsn=sqlitetest" 1
Just replace with MySql and you should be running fine

A minor thing, : but I noticed that you had:
let conf = OdbcConf "DNS=mysql_test;" 1 "MySQL" [should be DSN=]

{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
import Control.Monad.Logger
import Data.Text (Text)
import qualified Database.Esqueleto as E
import Database.Esqueleto ((^.))
import Yesod
import qualified Data.Conduit.List as CL
import Data.Conduit (($=))
import Prelude
import Database.Persist.ODBC
import Database.Persist.ODBCTypes
import Database.Persist.MigrateSqlite
import System.IO.Unsafe

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Author
name Text
Blog
author AuthorId
title Text
content Html
|]

data App = App
{ persistConfig :: OdbcConf
, connPool :: ConnectionPool
}
instance Yesod App

instance YesodPersist App where
type YesodPersistBackend App = SqlBackend
runDB = defaultRunDB persistConfig connPool

instance YesodPersistRunner App where
getDBRunner = defaultGetDBRunner connPool

mkYesod "App" [parseRoutes|
/ HomeR GET
/blog/#BlogId BlogR GET
|]

getHomeR :: Handler TypedContent
getHomeR = do
let blogsSrc =
E.selectSource
$ E.from $ (blog E.InnerJoin author) -> do
E.on $ blog ^. BlogAuthor E.==. author ^. AuthorId
return
( blog ^. BlogId
, blog ^. BlogTitle
, author ^. AuthorName
)
render <- getUrlRenderParams
respondSourceDB typeHtml $ do
sendChunkText "<title>Blog posts</title>

    "
    blogsSrc $= CL.map ((E.Value blogid, E.Value title, E.Value name) ->
    toFlushBuilder $
    [hamlet|

  • <a href=@{BlogR blogid}>#{title} by #{name}
    |] render
    )
    sendChunkText "
"

getBlogR :: BlogId -> Handler Html
getBlogR _ = error "Implementation left as exercise to reader"

main :: IO ()
main = do
-- Is this configuration right?
-- let conf = OdbcConf "DNS=mysql_test;" 1 "MySQL"
-- DRIVER=SQLite3 ODBC Driver;Database=c:\mydb.db;LongNames=0;Timeout=1000;NoTXN=0;SyncPragma=NORMAL;StepAPI=0;
-- let conf = OdbcConf "DRIVER=SQLite3 ODBC Driver;Database=mydb.db;LongNames=0;Timeout=1000;NoTXN=0;SyncPragma=NORMAL;StepAPI=0;" 1 "Sqlite {sqlite3619 = True}"
pool <- runStderrLoggingT $ createODBCPool (Just (Sqlite True)) "dsn=sqlitetest" 1
flip runSqlPersistMPool pool $ do
runMigration migrateAll

-- Fill in some testing data
alice <- insert $ Author "Alice"
bob   <- insert $ Author "Bob"

insert_ $ Blog alice "Alice's first post" "Hello World!"
insert_ $ Blog bob "Bob's first post" "Hello World!!!"
insert_ $ Blog alice "Alice's second post" "Goodbye World!"

warp 3000 App
{ persistConfig = undefined
, connPool = pool
}

It is working!
There is just a issue, when I run the first time with the migrations, a error appears, but then (after commenting it) it works apparently properly: (the error is in the last line)

Run from outside a project, using implicit global project config
Using resolver: lts-4.1 from implicit global project's config file: /home/geppetto/.stack/global-project/stack.yaml

getColumns cs=[Right (Left (Column {cName = DBName {unDBName = "name"}, cNull = False, cSqlType = SqlString, cDefault = Nothing, cDefaultConstraintName = Nothing, cMaxLen = Nothing, cReference = Nothing}))]

us=[]

old=[Right (Left (Column {cName = DBName {unDBName = "name"}, cNull = False, cSqlType = SqlString, cDefault = Nothing, cDefaultConstraintName = Nothing, cMaxLen = Nothing, cReference = Nothing}))]

fdefs=[]

getColumns cs=[Right (Left (Column {cName = DBName {unDBName = "author"}, cNull = False, cSqlType = SqlInt64, cDefault = Nothing, cDefaultConstraintName = Nothing, cMaxLen = Nothing, cReference = Nothing})),Right (Left (Column {cName = DBName {unDBName = "title"}, cNull = False, cSqlType = SqlString, cDefault = Nothing, cDefaultConstraintName = Nothing, cMaxLen = Nothing, cReference = Nothing})),Right (Left (Column {cName = DBName {unDBName = "content"}, cNull = False, cSqlType = SqlString, cDefault = Nothing, cDefaultConstraintName = Nothing, cMaxLen = Nothing, cReference = Nothing}))]

us=[]

old=[Right (Left (Column {cName = DBName {unDBName = "author"}, cNull = False, cSqlType = SqlInt64, cDefault = Nothing, cDefaultConstraintName = Nothing, cMaxLen = Nothing, cReference = Nothing})),Right (Left (Column {cName = DBName {unDBName = "title"}, cNull = False, cSqlType = SqlString, cDefault = Nothing, cDefaultConstraintName = Nothing, cMaxLen = Nothing, cReference = Nothing})),Right (Left (Column {cName = DBName {unDBName = "content"}, cNull = False, cSqlType = SqlString, cDefault = Nothing, cDefaultConstraintName = Nothing, cMaxLen = Nothing, cReference = Nothing}))]

fdefs=[]
Migrating: ALTER TABLE blog ADD CONSTRAINT blog_author_fkey FOREIGN KEY(author) REFERENCES author(id)
[Debug#SQL] ALTER TABLE blog ADD CONSTRAINT blog_author_fkey FOREIGN KEY(author) REFERENCES author(id); []
File.hs: Prelude.chr: bad argument: 5832776

gbwey commented

Unfortunately I don't have mysql and am unable to debug this.
Best,
Grant

Hello gbway, I am following another path. I am trying to integrate persistent-odbc with scotty, looking at this tutorial: http://adit.io/posts/2013-04-15-making-a-website-with-haskell.html

tutorial version using Postgrees:

import Database.Persist
import Database.Persist.Sqlite

runDb :: SqlPersist (ResourceT IO) a -> IO a
runDb query = runResourceT . withSqliteConn "dev.sqlite3" . runSqlConn $ query

but I am getting trouble in the following code:

import Prelude
import Database.Persist
import Database.Persist.ODBC

import Control.Monad.Logger (runNoLoggingT)
import Control.Monad.Trans.Resource (runResourceT, ResourceT)


runDb :: SqlPersist (ResourceT IO) a -> IO a
runDb = runResourceT . runNoLoggingT . (withODBCConn Nothing "dsn=newmyerp") . runSqlConn

But the signature doesn't match...