postgresqlhaskellhaskell-persistent

Inserting entry into database using PostGreSQL & Persistent


I'm trying to learn the Persistent Library.

I have two files one with all the types I've defined for my project (diet tracker)

Report.hs

data FoodEntry = FoodEntry { report :: Report
                           , date   :: UTCTime
                           }

data Report = Report { ...
                       ...
                     }

and another file used to generate tables

Storage.hs

import           Database.Persist.Sql                                                                                        
import qualified Database.Persist.TH  as PTH                                                                                 


PTH.share [ PTH.mkPersist PTH.sqlSettings                                                                                    
          , PTH.mkMigrate "migrateAll"                                                                                       
          ]                                                                                                                  
  [PTH.persistLowerCase|                                                                                                     
   FoodEntry sql=entries                                                                                                     
    report Report                                                                                                            
    date UTCTime default=now()                                                                                               
    UniqueDate date                                                                                                          
   Report sql=reports                                                                                                        
    name Text                                                                                                                
    reportingUnit Text                                                                                                       
    nutrients [Nutrient]                                                                                                     
   Nutrient sql=nutrients                                                                                                    
    nutrientName Text                                                                                                        
    nutrientUnit Text                                                                                                        
    nutrientValue Double Maybe                                                                                               
    deriving Show Read                                                                                                       
|] 

and a file that handles migration

import           Control.Monad.Logger                                                                                        
import           Database.Persist                                                                                            
import           Database.Persist.Postgresql                                                                                 
import           Types.Storage                                                                                               

connString :: ConnectionString                                                                                               
connString = "host=127.0.0.1 port=5432 user=postgres dbname=postgres password=password"                                      

runAction                                                                                                                    
  :: (MonadUnliftIO m, IsPersistBackend r,                                                                                   
      BaseBackend r ~ SqlBackend) =>                                                                                         
     ConnectionString -> ReaderT r (LoggingT m) a -> m a                                                                     
runAction connectionString action = runStdoutLoggingT                                                                        
                                    $ withPostgresqlConn connectionString                                                    
                                    $ \backend ->                                                                            
                                        runReaderT action backend                                                            

migrateDB :: IO ()                                                                                                           
migrateDB = runAction connString (runMigration migrateAll) 

In my [ProjectName.hs] file which main calls on

  args <- parseArgs -- args - optparse-applicative                                                                                                          

  if generateDB args == Just True                                                                                            
    then migrateDB                                                                                                           
    else case (...) of                                         
           ...                                 -> ...  
           ...                                 -> ...                                                
           (s, n, p, Just True)                -> undefined 

for the last case I want to add the user entry to the database.

I also have a function searchReport that consumes s, n & p to returns a Report type.

I want to do something like this

insertReport :: (MonadIO m) => UTCTime -> Report -> SqlPersistT m (Key FoodEntry)          
insertReport time report' = insert (FoodEntry report' time)

do date' <- Data.Time.Clock.getCurrentTime :: IO UTCTime
   report' <- searchReport s n p :: IO Report
   insertReport $ date' report'

But there are (AFAIK) two problem with this

  1. The type of Report.FoodEntry would not match the type Storage.FoodEntry I could write a function that'd convert one to the other but I wonder if there's a better way of doing that.

  2. If I were to test the function in that do block like this

    (s, n, p, Just True)                -> insertReport undefined undefined 
    

    I get error

    • Couldn't match type ‘ReaderT                                      
                         Database.Persist.Sql.Types.Internal.SqlBackend m0’                                                  
             with ‘IO’                            
    Expected type: IO ()                                                
    Actual type: persistent-2.9.2:Database.Persist.Sql.Types.SqlPersistT
                m0                                                                                                        
               (persistent-2.9.2:Database.Persist.Class.PersistEntity.Key          
                      Types.Storage.FoodEntry)                                                             
    

Solution

  • To run insertReport from IO monad you have to call runAction connectionString insertReport. This is what actually converts SqlPersistT to IO.

    As for difference between Report.FoodEntry and Storage.FoodEntry - why do you have two data types in first place? The entity you declared in PTH.share quasiquoter is also a valid Haskell data type, so you can use it like any other.

    However, if you really need to have 2 different FoodEntryes, then yes, you would need to write a funtion that converts between them.