Search code examples
jsonpostgresqlhaskellhaskell-spockpostgresql-simple

postgres-simple - No instance for (ToRow Int) arising from a use of ‘query’


I am new to haskell and have honestly hard times with it. But it expands my thinking, so here we go. I am trying to run a really simple Webserver that queries a Postgres DB and should return the result as JSON.

The query is absolutely simple: "Select id,data from MYTABLE where id = 1"

But the type system of haskell is killing me right now and the final type of my actions doesn't match up. I am using Spock and PostgreSQL-Simple as a Combo.

Most tutorials are either to simple for what I want to do or to difficult. I am somewhere inbetween and miss a lot of Haskell understanding, a lot of my previous problems I have already solved by simple copy and pasting and got a simple version working.

But as soon as I try to pass on a route variable I am failing. Here is my working version. My Database Table is here called "Envelope", The important call is where it says get "json" :

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}

module Main where

import Web.Spock
import Web.Spock.Config
import Database.PostgreSQL.Simple
import Data.Pool
import Data.Aeson (ToJSON(toJSON), object, (.=),Value)
import  Database.PostgreSQL.Simple.FromRow

type AppAction a = SpockActionCtx () Connection AppSession AppState a

data AppState = EmptyState
data AppSession = EmptySession

data Envelope = Envelope { envId :: Int, envData :: Value } deriving Show

instance FromRow Envelope where
  fromRow = Envelope <$> field <*> field

instance ToJSON Envelope where
   toJSON (Envelope envA envB) = object [ "id" .= envA, "data" .= envB ]

main :: IO ()
main =
  do pool<-createPool (connect (ConnectInfo "localhost" 5432 "" "" "envelopes") ) close 1 10 10
     spockCfg <- defaultSpockCfg EmptySession (PCPool pool) EmptyState
     runSpock 8080 (spock spockCfg app)

app :: SpockM Connection AppSession AppState ()
app = do 
    get root $
      text "Hello World!"
    get "json" $ do
      xs<-runQuery $ \conn -> 
        query_ conn  "select id,data from envelope where id = 1"
      json (xs::[Envelope])

Then I try to pass on the Envelope ID with a lambda function, for that i also need to change PostgreSQL-Simple query_ to query:

    get ( "json" <//> var  ) $ \eid -> do
      xs<-runQuery $ \conn -> 
        query conn  "select id,data from envelope where id = ?" (eid :: Int)
      json (xs::[Envelope])

The error I get says:

 No instance for (ToRow Int) arising from a use of ‘query’
   In the expression:
      query conn "select id,data from envelope where id = ?" (eid :: Int)
    In the second argument of ‘($)’, namely
      ‘\ conn
         -> query
              conn "select id,data from envelope where id = ?" (eid :: Int)’
    In a stmt of a 'do' block:
      xs <- runQuery
            $ \ conn
                -> query
                     conn "select id,data from envelope where id = ?" (eid :: Int)

I also have problem to return just the first item from the query, even without the lambda function.

Full Source can be found on bitbucket

I hope someone has the time to help me out here. Thanks for reading already.


Solution

  • What the error basically says is that you can't pass an Int to query as the third argument. query expects something that has an instance of the typeclass ToRow and Int is not one. What you might want to do in your case considering you only want to pass one value to query is to use Only. So that line becomes:

    query conn  "select id,data from envelope where id = ?" (Only (eid :: Int))