0

I want to run a query against my table for a given value, and return a Maybe a depending on whether a row was found.

I have this domain:

data User' a b c d e f = User {  usrId :: a,
                                     usrApproved :: b,
                                     usrIden :: c, 
                                     usrImgUrl :: d,
                                     usrTitle :: e, 
                                     usrUrl :: f 
                                  }

type User = User' Int Bool String String String String

$(makeAdaptorAndInstance "pUser" ''User')

type UserColumn = User' (Column PGInt4) (Column PGBool) (Column PGText) (Column PGText) (Column PGText) (Column PGText) 

and the following definitions for the table and queries:

userTable :: Table UserColumn UserColumn
userTable = Table "user" (pUser User {  usrId       = required "id",
                                                usrApproved = required "approved",
                                                usrIden     = required "identifier",
                                                usrImgUrl   = required "img_url",
                                                usrTitle    = required "title",
                                                usrUrl      = required "url"
                                             })

userQuery :: Query UserColumn
userQuery = queryTable userTable

As hinted previously, I want to query by the "identifier" column, so I've written this query and wish to return a `IO (Maybe User)

userByIdenQuery :: (Column PGText) -> Query UserColumn
userByIdenQuery iden = proc () -> do
  user <- userQuery -< ()
  restrict -< (adIden user) .=== iden
  returnA -< user    

getUserByIden :: String -> PGS.Connection -> IO (Maybe User)
getUserByIden iden c = do
  usr <- runQuery c (userByIdenQuery $ pgString iden)
  -- But this fails to compile
  undefined -- just to minimise compilation errors

this fails to compile with:

No instance for (Default
                   Opaleye.Internal.RunQuery.QueryRunner UserColumn haskells0)
  arising from a use of `runQuery'
The type variable `haskells0' is ambiguous
Note: there is a potential instance available:
  instance (product-profunctors-0.7.1.0:Data.Profunctor.Product.Class.ProductProfunctor
              p,
            Default p a1_0 a1_1, Default p a2_0 a2_1, Default p a3_0 a3_1,
            Default p a4_0 a4_1, Default p a5_0 a5_1, Default p a6_0 a6_1) =>
           Default
             p
             (User' a1_0 a2_0 a3_0 a4_0 a5_0 a6_0)
             (User' a1_1 a2_1 a3_1 a4_1 a5_1 a6_1)
    -- Defined at src\DB.hs:33:3
In a stmt of a 'do' block:
  usr <- runQuery c (userByIdenQuery $ pgString iden)
In the expression:
  do { usr <- runQuery c (userByIdenQuery $ pgString iden);
       undefined }
In an equation for `getUserByIden':
    getUserByIden iden c
      = do { usr <- runQuery c (userByIdenQuery $ pgString iden);
             undefined }

If I try to implement the function:

getUserByIden :: String -> PGS.Connection -> IO (Maybe User)
getUserByIden iden c = do
  (usrId, appr, idn, imUrl, tit, url) <- runQuery c (userByIdenQuery $ pgString iden)
  return $ Just $ User usrId appr idn imUrl tit url

Then I'm presented with this compilation error:

Couldn't match expected type `[haskells0]'
        with actual type `(Int, Bool, String, String, String, String)'
In the pattern: (usrId, appr, idn, imUrl, tit, url)
In a stmt of a 'do' block:
  (usrId, appr, idn, imUrl, tit, url) <- runQuery
                                          c (userByIdenQuery $ pgString iden)
In the expression:
  do { (usrId, appr, idn, imUrl, tit, url) <- runQuery
                                               c (userByIdenQuery $ pgString iden);
       return $ Just $ User usrId appr idn imUrl tit url }

I really have no idea where to go with this, other than using a library other than Opaleye.
Alex
  • 8,093
  • 6
  • 49
  • 79
  • 1
    `runQuery` has a very general return type - give it a type signature, or the return value a type signature. The [docs](https://hackage.haskell.org/package/opaleye-0.5.0.0/docs/Opaleye-RunQuery.html) very clearly state: "runQuery's use of the Default typeclass means that the compiler will have trouble inferring types. It is strongly recommended that you provide full type signatures when using runQuery." – user2407038 Sep 03 '16 at 12:55
  • How do I provide the type signature exactly? The haddock always confused me there. – Alex Sep 03 '16 at 13:48
  • 1
    In a sense, you have given it a type signature already - `getUserByIden :: String -> PGS.Connection -> IO (Maybe User)` - but `usr` is unused in the body so it cannot unify with `User`. If you actually implement the function, it may be sufficient. In the meantime you can write a type signature in any number of ways: `(x :: X) <- a` or `x <- a :: IO X` or `_x <- a; let x :: X; x = _x` – user2407038 Sep 03 '16 at 13:54
  • 1
    @atc Did you find a satisfactory solution to this? If not feel free to ask me directly by [filing an issue](https://github.com/tomjaguarpaw/haskell-opaleye/issues) or by [emailing me](http://web.jaguarpaw.co.uk/%7Etom/contact/). – Tom Ellis Sep 03 '16 at 22:27
  • Thanks Tom, I did. The accepted answer was spot-on. Would've been nice to have this covered in the tutorial but that's me being pedantic :) – Alex Sep 03 '16 at 23:02

2 Answers2

3

I'm not familiar with OpalEye, but I think you might just need a judicious use of listToMaybe (import Data.Maybe (listToMaybe))

getUserByIden :: String -> PGS.Connection -> IO (Maybe User)
getUserByIden iden c = do
  listToMaybe <$> runQuery c (userByIdenQuery $ pgString iden)
NovaDenizen
  • 5,089
  • 14
  • 28
1

I think the problem lies with you using undefined in the end of your function getUserByIden. Because of some complicated type level magic Opaleye is doing under the hood, you need to specify a type for the use of runQuery, or simply return the value produced instead of undefined, because undefined throws the top inference algorithm off a little bit:

getUserByIden :: String -> PGS.Connection -> IO (Maybe User)
getUserByIden iden c = listToMaybe <$> runQuery c (userByIdenQuery $ pgString iden)

I also added a listToMaybe call to change the type of the body of the function to match the return type you specified. runQuery, when fully applied, returns a value of type IO [SomeHaskellType], not IO (Maybe SomeHaskellType).

ThomasD
  • 31
  • 1
  • 3
  • Thanks. Another answer (similar content) was posted before yours, so I've accepted theirs. Thank you for your help! – Alex Sep 03 '16 at 15:46