3

I am fairly new to Haskell and have been experimenting with yesod for about a week now. I have been trying to connect to an existing database that has a composite primary key in sqlite. I managed to get the code to work with Database.Persist.Sqlite as a standalone application.

Here is the code that works as a standalone application using persistent-sqlite.

{-# LANGUAGE EmptyDataDecls             #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE GADTs                      #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE QuasiQuotes                #-}
{-# LANGUAGE TemplateHaskell            #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DeriveGeneric #-}
import           Control.Monad.IO.Class  (liftIO)
import           Database.Persist
import           Database.Persist.Sqlite
import           Database.Persist.TH
import           System.Environment
import           Data.Text (Text,pack)
import           Data.Time (UTCTime)

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
    Movie
    title Text maxlen=20
    year Int maxlen=11
    genre Text Maybe maxlen=128
    mpaa Text Maybe maxlen=16
    director Text Maybe maxlen=128
    actors Text Maybe maxlen=512
    description Text Maybe maxlen=512
    path Text Maybe maxlen=128
    codec Text Maybe maxlen=32
    length Int Maybe maxlen=11
    poster Text Maybe maxlen=128
    added UTCTime default=CURRENT_TIMESTAMP
    Primary title year
    deriving Show
|]

main :: IO ()
main = do
  (path:args) <- getArgs
  movies <- runSqlite (pack path) $ do
    runMigration migrateAll
    selectList [] [Desc MovieTitle]

  mapM_ print movies

This is sort of a contrived example, but it illustrates my point. I would like to have a composite primary key that consists of title and year. Everything compiles fine and the table is created with the schema that I need. When I try to use the Movie type as defined above in my yesod application with persistent-sqlite, I get the following compile-time error

Foundation.hs:35:1:
    No instance for (PathPiece MovieId)
      arising from a use of ‘toPathPiece’
    In the first argument of ‘(:)’, namely ‘(toPathPiece dyn_apvA)’
    In the second argument of ‘(:)’, namely
      ‘((toPathPiece dyn_apvA) : [])’
    In the expression:
      ((Data.Text.pack "entry") : ((toPathPiece dyn_apvA) : []))

Foundation.hs:35:1:
    No instance for (PathPiece MovieId)
      arising from a use of ‘fromPathPiece’
    In the expression: fromPathPiece
    In the pattern: fromPathPiece -> Just dyn_apwt
    In the pattern: (:) (fromPathPiece -> Just dyn_apwt) []

I am stuck at this point and unable to continue on my own. I have tried searching, but I am only able to find a working example of composite primary keys without the use of yesod. I have a feeling that it should be possible to do since the composite primary key using only persistent-sqlite works.

Here is how the Movie type is defined in config/models

Movie
    title Text maxlen=20
    year Int maxlen=11
    genre Text Maybe maxlen=128
    mpaa Text Maybe maxlen=16
    director Text Maybe maxlen=128
    actors Text Maybe maxlen=512
    description Text Maybe maxlen=512
    path Text Maybe maxlen=128
    codec Text Maybe maxlen=32
    length Int Maybe maxlen=11
    poster Text Maybe maxlen=128
    added UTCTime default=CURRENT_TIMESTAMP
    Primary title year
    deriving
ulbrec
  • 65
  • 4

1 Answers1

3

I haven't actually used the composite primary key feature myself, but this error makes sense to me. There's not obvious serialization to Text for arbitrary composite keys, and so persistent doesn't generate it for you. If you'd like to use a MovieId inside your URLs, you'll need to manually define the PathPiece instance, which is just a pair of functions for converting to and from Text.

Michael Snoyman
  • 31,100
  • 3
  • 48
  • 77