Skip to content

Instantly share code, notes, and snippets.

@domenkozar
Created November 25, 2020 15:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save domenkozar/fbdb253014915464b8196c2c6d520ba3 to your computer and use it in GitHub Desktop.
Save domenkozar/fbdb253014915464b8196c2c6d520ba3 to your computer and use it in GitHub Desktop.
Hasql nested records and maybe/nullable

Start postgresql:

docker run --rm --name postgresql -e POSTGRES_PASSWORD=password -p 5432:5432 postgres

Connect to it in separate terminal:

docker exec -it postgresql psql -U postgres

And populate DB:

CREATE TABLE animal (id SERIAL PRIMARY KEY, name TEXT NOT NULL);
CREATE TABLE house (title TEXT NOT NULL, location TEXT NOT NULL, animal INTEGER REFERENCES animal);
INSERT INTO animal (name) VALUES ('dog');

And run our query which results into unexpected result given how line 31 is defined:

$ ./HaSQLNestedRecord.hs 
Left (QueryError "SELECT animal.id, animal.name, house.title, house.location FROM animal LEFT JOIN house ON house.animal = animal.id WHERE animal.id = $1" ["1"] (ResultError (RowError 0 UnexpectedNull)))
#!/usr/bin/env stack
-- stack --nix --nix-packages postgresql --resolver lts-16.23 --install-ghc runghc --package hasql --package text --package bytestring
{-# LANGUAGE OverloadedStrings #-}
import Data.Text
import Data.ByteString
import Data.Int
import qualified Hasql.Decoders as HD
import qualified Hasql.Encoders as HE
import qualified Hasql.Statement as HQ
import qualified Hasql.Connection as HC
import qualified Hasql.Session as HS
data Animal = Animal
{ id :: Int32
, name :: Text
, house :: Maybe House
} deriving Show
data House = House
{ title :: Text
, location :: Text
} deriving Show
decoder :: HD.Row Animal
decoder =
Animal
<$> fmap fromIntegral ((HD.column . HD.nonNullable) HD.int4)
<*> (HD.column . HD.nonNullable) HD.text
<*> fmap Just houseDecoder
houseDecoder :: HD.Row House
houseDecoder =
House
<$> (HD.column . HD.nonNullable) HD.text
<*> (HD.column . HD.nonNullable) HD.text
selectOneAnimal :: HQ.Statement Int32 Animal
selectOneAnimal =
HQ.Statement sql ((HE.param . HE.nonNullable) HE.int4) (HD.singleRow decoder) True
where
sql :: ByteString
sql =
"SELECT animal.id, animal.name, house.title, house.location \
\FROM animal \
\LEFT JOIN house ON house.animal = animal.id \
\WHERE animal.id = $1"
main :: IO ()
main = do
eitherConnection <- HC.acquire "postgresql://postgres:password@localhost/postgres"
case eitherConnection of
Left err -> print err
Right connection -> do
animal <- HS.run (HS.statement 1 selectOneAnimal) connection
print animal
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment