How can we use Yesod with persistent-odbc? For instance the following code is for using Yesod with Sqlite:
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
import Yesod
import Database.Persist.Sqlite
import Control.Monad.Trans.Resource (runResourceT)
import Control.Monad.Logger (runStderrLoggingT)
-- Define our entities as usual
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
firstName String
lastName String
age Int
deriving Show
|]
-- We keep our connection pool in the foundation. At program initialization, we
-- create our initial pool, and each time we need to perform an action we check
-- out a single connection from the pool.
data PersistTest = PersistTest ConnectionPool
-- We'll create a single route, to access a person. It's a very common
-- occurrence to use an Id type in routes.
mkYesod "PersistTest" [parseRoutes|
/ HomeR GET
/person/#PersonId PersonR GET
|]
-- Nothing special here
instance Yesod PersistTest
-- Now we need to define a YesodPersist instance, which will keep track of
-- which backend we're using and how to run an action.
instance YesodPersist PersistTest where
type YesodPersistBackend PersistTest = SqlBackend
runDB action = do
PersistTest pool <- getYesod
runSqlPool action pool
-- List all people in the database
getHomeR :: Handler Html
getHomeR = do
people <- runDB $ selectList [] [Asc PersonAge]
defaultLayout
[whamlet|
<ul>
$forall Entity personid person <- people
<li>
<a href=#{PersonR personid}>#{personFirstName person}
|]
-- We'll just return the show value of a person, or a 404 if the Person doesn't
-- exist.
getPersonR :: PersonId -> Handler String
getPersonR personId = do
person <- runDB $ get404 personId
return $ show person
openConnectionCount :: Int
openConnectionCount = 10
main :: IO ()
main = runStderrLoggingT $ withSqlitePool "test.db3" openConnectionCount $ \pool -> liftIO $ do
runResourceT $ flip runSqlPool pool $ do
runMigration migrateAll
insert $ Person "Michael" "Snoyman" 26
warp 3000 $ PersistTest pool
How would it be like if instead of Database.Persist.Sqlite we used Database.Persist.ODBC?
Related
Hi and thanks for your time.
I'm trying to create a website that features a button that increments a counter. I want the current counter to be persistent and if somebody goes to my page, the current counter should be displayed.
A request should be send every time I click the button to increment the counter. The request does not contain any information about the counter value. The server - in my case a warp web server - should update the counter value in the database, read the value after the update and then send it to the frontend if successful, of an error message if not.
So far, only the updating works, since I did not manage to figure out how to get the data from the database to the frontend.
Here is the code from my Repository module that should do the updating:
{-# LANGUAGE EmptyDataDecls, FlexibleContexts, GADTs, GeneralizedNewtypeDeriving#-}
{-# LANGUAGE MultiParamTypeClasses, OverloadedStrings, QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell, TypeFamilies, DataKinds, FlexibleInstances#-}
{-# LANGUAGE DerivingStrategies, StandaloneDeriving, UndecidableInstances #-}
module Repository (increaseCounter) where
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Logger (runStderrLoggingT)
import Database.Persist
import Database.Persist.Sqlite
import Database.Persist.TH
import Control.Monad.Reader
import Data.Text
import Data.Maybe
-- setting up the Counter entity with a unique key so I can use the getBy function
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Counter
counterName String
counterCount Int Maybe
UniqueCounterName counterName
deriving Show
|]
increaseCounter :: IO ()
increaseCounter =
runStderrLoggingT $ withSqliteConn "//absolute/path/database.db" $ runSqlConn $ do
runMigration migrateAll -- only for developing
updateWhere [CounterCounterName ==. "unique name"] [CounterCounterCount +=. Just 1]
counterEntity <- getBy $ UniqueCounterName name
liftIO $ print counterEntity
This compiles and actually persists the counter and updates the value every time its called. But as you can tell from the types, after update it only prints the counter value to the console.
I seem to have problems understanding how to use the data that is returned from the getBy function.
The docs say:
getBy :: (PersistUniqueRead backend, MonadIO m, PersistRecordBackend record backend) =>
Unique record -> ReaderT backend m (Maybe (Entity record))
Is the 'backend m' basically a nested monad?
Assuming I simply want to send the value of the counter if it is Just Int and I want to return -1 if it is Nothing.
I assume I can not modify the increaseCounter function so that its type is Maybe Int. But how do I pass functions into the monad / access the value inside to send a response to the frontend?
If this question is to superficial and/or I lack too much knowledge to proceed at this point, can you recommend good sources for information? Something like a good tutorial or youtube channel or something?
Thanks!
You can ignore all the monadic parts of getBy's type signature. Provided you get your code to type check, counterEntity has type Maybe (Entity Counter), and that's all that's important here.
The counterEntity is Nothing if the query fails (i.e., no record in the table for that counter). Otherwise, it's Just an Entity Counter containing the retrieved record:
case counterEntity of
Just e -> ...
This e :: Entity Counter can be turned into a Counter via entityVal. The desired field of that Counter can be extracted with counterCounterCount. The result will be a Maybe Int because you've tagged that field as Maybe, so you'll have another layer of Maybe to unpack:
case counterEntity of
Nothing -> -1 -- no record for this counter
Just e -> case counterCounterCount (entityVal e) of
Nothing -> -1 -- record, but counter value missing
Just v -> v
You'll want to return this value from increaseCounter, so the final version will look like this:
increaseCounter :: IO Int
increaseCounter =
runStderrLoggingT $ withSqliteConn "//absolute/path/database.db" $ runSqlConn $ do
runMigration migrateAll -- only for developing
updateWhere [CounterCounterName ==. "unique name"] [CounterCounterCount +=. Just 1]
counterEntity <- getBy $ UniqueCounterName "unique name"
return $ case counterEntity of
Nothing -> -1
Just e -> case counterCounterCount . entityVal $ e of
Nothing -> -1
Just v -> v
Wherever you previously successfully used increaseCounter to increase the counter, you'll now want to write:
updatedCounterValue <- increaseCounter
and you can pass the plain old updatedCounterValue :: Int to the front end.
You might find it more sensible to use upsertBy, which can insert the counter record if it's missing and update it otherwise. It also returns the inserted/updated entity, saving you a separate getBy call. I also don't understand why you've tagged counterCount with Maybe. Why would you insert a counter into your table with no value? Wouldn't "0" be a better starting value if you wanted to indicate "no count"?
So, I'd rewrite the schema as:
Counter
counterName String
counterCount Int -- no Maybe
UniqueCounterName counterName
deriving Show
and the increaseCounter function as:
increaseCounter :: IO Int
increaseCounter =
runStderrLoggingT $ withSqliteConn "//absolute/path/database.db" $ runSqlConn $ do
runMigration migrateAll -- only for developing
let name = "unique name"
counterEntity <- upsertBy (UniqueCounterName name)
(Counter name 1)
[CounterCounterCount +=. 1]
return $ counterCounterCount (entityVal counterEntity)
To either insert a 1-count or increase an existing count.
Finally, as a general design approach, it's probably better to move the database migration and connection setup into the main function, and maybe use a pool of connections, something like:
#!/usr/bin/env stack
-- stack --resolver lts-18.0 script
-- --package warp
-- --package persistent
-- --package persisent-sqlite
{-# LANGUAGE EmptyDataDecls, FlexibleContexts, GADTs, GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses, OverloadedStrings, QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell, TypeFamilies, DataKinds, FlexibleInstances#-}
{-# LANGUAGE DerivingStrategies, StandaloneDeriving, UndecidableInstances #-}
import Control.Monad.Logger (runStderrLoggingT)
import Database.Persist
import Database.Persist.TH
import Database.Persist.Sqlite
import Control.Monad.Reader
import Network.HTTP.Types
import Network.Wai
import Network.Wai.Handler.Warp
import qualified Data.ByteString.Lazy.Char8 as C8
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Counter
counterName String
counterCount Int
UniqueCounterName counterName
deriving Show
|]
increaseCounter :: ReaderT SqlBackend IO Int
increaseCounter = do
let name = "unique name"
counterEntity <- upsertBy (UniqueCounterName name)
(Counter name 1)
[CounterCounterCount +=. 1]
return $ counterCounterCount (entityVal counterEntity)
main :: IO ()
main = runStderrLoggingT $ withSqlitePool "some_database.db" 5 $ \pool -> do
runSqlPool (runMigration migrateAll) pool
let runDB act = runSqlPool act pool
liftIO $ run 3000 $ \req res -> do
count <- runDB $ increaseCounter
res $ responseLBS
status200
[("Content-Type", "text/plain")]
(C8.pack $ show count ++ "\n")
Similar question: Haskell / Persistent-Sqlite: "No instance for (Control.Monad.Trans.Resource.MonadResource IO)"
I'm trying the use the selectSource from persistent to read the database in streaming mode and log the values in the console using logInfo.
The following snippets are executed in a RIO project template.
The following code contains the parts that work and don't work.
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Disambiguate.People where
import Data.Conduit
import qualified Data.Conduit.List as CL
import Database.Persist
import Database.Persist.Sqlite
import Database.Persist.TH
import Import hiding (on, (^.))
import Prelude (print)
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
name String
age Int
deriving Show
|]
disambiguatePeople :: RIO App ()
disambiguatePeople = do
-- WORKS!
runSqlite ":memory:" insertAndRead
-- DOES NOT COMPILE!
withSqlitePool ":memory:" 1 $ \pool -> do
liftSqlPersistMPool insertAndRead pool
where
insertAndRead = do
runMigration migrateAll
insert_ $ Person "John Doe" 35
insert_ $ Person "Jane Doe" 50
runConduit $ selectSource [PersonAge >. 5] [] .| CL.mapM_
(lift . logInfo . displayShow)
I get the following error:
src/Run.hs:37:25: error:
• Couldn't match type ‘RIO App’ with ‘IO’
Expected type: ReaderT
SqlBackend
(monad-logger-0.3.35:Control.Monad.Logger.NoLoggingT
(Control.Monad.Trans.Resource.Internal.ResourceT IO))
()
Actual type: ReaderT
SqlBackend
(monad-logger-0.3.35:Control.Monad.Logger.NoLoggingT
(Control.Monad.Trans.Resource.Internal.ResourceT (RIO App)))
()
• In the first argument of ‘liftSqlPersistMPool’, namely
‘insertAndRead’
In a stmt of a 'do' block: liftSqlPersistMPool insertAndRead pool
In the expression: do liftSqlPersistMPool insertAndRead pool
|
37 | liftSqlPersistMPool insertAndRead pool
| ^^^^^^^^^^^^^
I understand the error since the function liftSqlPersistMPool hardcoded the IO type.
How can I solve this to use it with the RIO type ?
The following code launches a web server at localhost:3000.
#!/usr/bin/env stack
-- stack script --resolver=lts-12.16
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
import Yesod
import Database.Persist.Sqlite
import Control.Monad.Trans.Resource (runResourceT)
import Control.Monad.Logger (runStderrLoggingT)
import Data.Text
-- Define our entities as usual
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Book
title Text
author Text
publisher Text
date Text
price Int
deriving Show
|]
-- We keep our connection pool in the foundation. At program initialization, we
-- create our initial pool, and each time we need to perform an action we check
-- out a single connection from the pool.
data SQLi = SQLi ConnectionPool
-- We'll create a single route, to access a person. It's a very common
-- occurrence to use an Id type in routes.
mkYesod "SQLi" [parseRoutes|
/ HomeR GET
/book/#BookId BookR GET
|]
-- Nothing special here
instance Yesod SQLi
-- Now we need to define a YesodPersist instance, which will keep track of
-- which backend we're using and how to run an action.
instance YesodPersist SQLi where
type YesodPersistBackend SQLi = SqlBackend
runDB action = do
SQLi pool <- getYesod
runSqlPool action pool
searchFrame :: [Entity Book] -> Widget
searchFrame books =
[whamlet|
<table border=1>
<tr>
<th>ID
<th>Title
<th>Author
<th>Publisher
<th>Publication date
<th>Price
$forall Entity bookid book <- books
<tr>
<td>
<a href=#{BookR bookid}>#{show bookid}
<td>
#{bookTitle book}
<td>
#{bookAuthor book}
<td>
#{bookPublisher book}
<td>
#{bookDate book}
<td>
#{bookPrice book}
|]
-- List all people in the database
getHomeR :: Handler Html
getHomeR = do
mBookAuthor <- lookupGetParam "author"
maybe (do
books <- runDB $ selectList [] [Asc BookId]
defaultLayout $ searchFrame books)
(¥author -> do
books <- runDB $ selectList [BookAuthor ==. author] [Asc BookId]
defaultLayout $ [whamlet|
<h1>Search books manually
^{searchFrame books}
|]) $ mBookAuthor
-- We'll just return the show value of a person, or a 404 if the Person doesn't
-- exist.
getBookR :: BookId -> Handler String
getBookR bookId = do
book <- runDB $ get404 bookId
return $ show book
openConnectionCount :: Int
openConnectionCount = 10
main :: IO ()
main = runStderrLoggingT $ withSqlitePool ":memory:" openConnectionCount $ ¥pool -> liftIO $ do
runResourceT $ flip runSqlPool pool $ do
runMigration migrateAll
insert $ Book "A Midsummer Night's Dream" "Shakespeare" "A bookstore" "1979/01" 600
insert $ Book "Hamlet" "Shakespeare" "B bookstore" "1997/04" 1260
insert $ Book "Macbeth" "Shakespeare" "C bookstore" "2001/05" 1530
insert $ Book "King Lear" "Shakespeare" "D bookstore" "2004/07" 1890
warp 3000 $ SQLi pool
Then, loading http://localhost:3000/?author=' AND EXTRACTVALUE(0, (SELECT CONCAT('$', id, ':', pwd) FROM users LIMIT 0, 1)) # sometimes results 500 Internal Server Error and this error message is issued even if we load normal pages such as http://localhost:3000/
Actually, this could be an attack vector for SQL injection. So, I'm confused with weather this is caused by an attack, or something else. Can someone explain this behavior?
Oops. I missed that withSqlitePool should not be used with :memory:. By giving it a file name, the problem fixes.
I'm new in Haskell. I woudl like to create simple crud rest api with scotty and persistence. I'm using sqlite with one table todo (title text, description text), I have some records in table. My point is to show all records on enpoint /todos in json format. When I print it I gets an error
• Couldn't match expected type ‘Data.Text.Internal.Lazy.Text’
with actual type ‘[Entity ToDo]’
• In the first argument of ‘text’, namely ‘(_ToDo)’
In a stmt of a 'do' block: text (_ToDo)
In the second argument of ‘($)’, namely
‘do _ToDo <- liftIO readToDo
text (_ToDo)’
|
69 | text(_ToDo)
| ^^^^^
My code:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Main where
import Data.Monoid ((<>))
import Web.Scotty
import qualified Web.Scotty as S
import Database.Persist
import Database.Persist.Sqlite
import Database.Persist.TH
import Data.Text (Text)
import Data.Time (UTCTime, getCurrentTime)
import qualified Data.Text as T
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Resource (runResourceT, ResourceT)
import Database.Persist.Sql
import Control.Monad (forM_)
import Control.Applicative
import Control.Monad.Logger
import Data.Aeson
import Data.Default.Class
import GHC.Generics
import Control.Monad.IO.Class (liftIO)
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
ToDo
title String
description String
deriving Show
|]
runDb :: SqlPersist (ResourceT (NoLoggingT IO)) a -> IO a
runDb = runNoLoggingT
. runResourceT
. withSqliteConn "todo.db"
. runSqlConn
instance ToJSON ToDo where
toJSON(ToDo title description) = object ["title" .= title, "description" .= description]
readToDo :: IO [Entity ToDo]
readToDo = (runDb $ selectList [] [LimitTo 10] )
routes :: ScottyM()
routes = do
S.get "/hello" $ do
text "hello world!"
S.get "/hello/:name" $ do
name <- param "name"
text ("hello" <> name <> "!")
S.get "/todos" $ do
_ToDo <- liftIO readToDo
text(_ToDo)
main = do
putStrLn "Starting server...."
scotty 7777 routes
How to correct this?
It looks like compiler accused you of having a bad conversion between the list of Entity ToDo-s and Data.Text.Lazy.
Please try to convert it by hand using pack function:
import Data.Text.Lazy (pack)
...
routes = do
S.get "/hello" $ do
text "hello world!"
S.get "/hello/:name" $ do
name <- param "name"
text ("hello" <> name <> "!")
S.get "/todos" $ do
_ToDo <- liftIO readToDo
text(pack . show $ _ToDo) -- explicit conversion
...
I have an Entity record, specifically Entity User and I need to extract the Id which the User has in the database as an Int.
From reading the docs it seems entityKey would be useful here but I'm not quite sure how I would go about getting an Int out.
You have to use a combination of fromSqlKey and entityKey. A sample program demonstrating it:
#!/usr/bin/env stack
{- stack
--resolver lts-9.0
--install-ghc
runghc
--package persistent
--package persistent-sqlite
--package persistent-template
--package mtl
-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Control.Monad.IO.Class (liftIO, MonadIO)
import Control.Monad.Reader
import Database.Persist
import Database.Persist.Sqlite
import Database.Persist.TH
import Data.Int
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
name String
age Int Maybe
deriving Show
|]
insertPerson :: MonadIO m => ReaderT SqlBackend m (Key Person)
insertPerson = insert $ Person "Michael" $ Just 26
main :: IO ()
main = runSqlite ":memory:" $ do
runMigration migrateAll
michaelId <- insertPerson
(michael :: Entity Person) <- getJustEntity michaelId
liftIO $ print $ (fromSqlKey . entityKey $ michael :: Int64)
And it's output:
~/g/scripts $ stack persist.hs
Migrating: CREATE TABLE "person"("id" INTEGER PRIMARY KEY,"name" VARCHAR NOT NULL,"age" INTEGER NULL)
1