hasql-1.10: src/library-tests/Sharing/ByUnit/Decoders/EnumSpec.hs
module Sharing.ByUnit.Decoders.EnumSpec (spec) where
import Data.HashSet qualified as HashSet
import Hasql.Connection qualified as Connection
import Hasql.Decoders qualified as Decoders
import Hasql.Errors qualified as Errors
import Hasql.Session qualified as Session
import Hasql.Statement qualified as Statement
import Helpers.Scripts qualified as Scripts
import Test.Hspec
import Prelude
spec :: SpecWith (Text, Word16)
spec = do
describe "Simple enums" do
it "decodes a simple named enum from static SQL" \config -> do
enumName <- Scripts.generateSymname
Scripts.onPreparableConnection config \connection -> do
result <- Connection.use connection do
-- Create enum type
Session.statement ()
$ Statement.preparable
(mconcat ["create type ", enumName, " as enum ('sad', 'ok', 'happy')"])
mempty
Decoders.noResult
-- Test decoding from static value
Session.statement ()
$ Statement.preparable
(mconcat ["select 'happy' :: ", enumName])
mempty
(Decoders.singleRow (Decoders.column (Decoders.nonNullable (Decoders.enum Nothing enumName (Just . id)))))
result `shouldBe` Right "happy"
it "decodes different enum values" \config -> do
enumName <- Scripts.generateSymname
Scripts.onPreparableConnection config \connection -> do
result <- Connection.use connection do
-- Create enum type
Session.statement ()
$ Statement.preparable
(mconcat ["create type ", enumName, " as enum ('alpha', 'beta', 'gamma')"])
mempty
Decoders.noResult
-- Test decoding multiple values
r1 <-
Session.statement ()
$ Statement.preparable
(mconcat ["select 'alpha' :: ", enumName])
mempty
(Decoders.singleRow (Decoders.column (Decoders.nonNullable (Decoders.enum Nothing enumName (Just . id)))))
r2 <-
Session.statement ()
$ Statement.preparable
(mconcat ["select 'gamma' :: ", enumName])
mempty
(Decoders.singleRow (Decoders.column (Decoders.nonNullable (Decoders.enum Nothing enumName (Just . id)))))
return (r1, r2)
result `shouldBe` Right ("alpha", "gamma")
describe "Enums in composites" do
it "decodes enums nested in named composites from static SQL" \config -> do
enumName <- Scripts.generateSymname
compositeName <- Scripts.generateSymname
Scripts.onPreparableConnection config \connection -> do
result <- Connection.use connection do
-- Create enum type
Session.statement ()
$ Statement.preparable
(mconcat ["create type ", enumName, " as enum ('red', 'green', 'blue')"])
mempty
Decoders.noResult
-- Create composite type with enum
Session.statement ()
$ Statement.preparable
(mconcat ["create type ", compositeName, " as (id int8, color ", enumName, ")"])
mempty
Decoders.noResult
-- Test decoding
Session.statement ()
$ Statement.preparable
(mconcat ["select (42, 'green') :: ", compositeName])
mempty
( Decoders.singleRow
( Decoders.column
( Decoders.nonNullable
( Decoders.composite
Nothing
compositeName
( (,)
<$> Decoders.field (Decoders.nonNullable Decoders.int8)
<*> Decoders.field (Decoders.nonNullable (Decoders.enum Nothing enumName (Just . id)))
)
)
)
)
)
result `shouldBe` Right (42 :: Int64, "green")
it "decodes multiple levels of nesting with enums" \config -> do
enumName <- Scripts.generateSymname
innerType <- Scripts.generateSymname
outerType <- Scripts.generateSymname
Scripts.onPreparableConnection config \connection -> do
result <- Connection.use connection do
-- Create enum type
Session.statement ()
$ Statement.preparable
(mconcat ["create type ", enumName, " as enum ('small', 'medium', 'large')"])
mempty
Decoders.noResult
-- Create inner composite with enum
Session.statement ()
$ Statement.preparable
(mconcat ["create type ", innerType, " as (size ", enumName, ", count int4)"])
mempty
Decoders.noResult
-- Create outer composite
Session.statement ()
$ Statement.preparable
(mconcat ["create type ", outerType, " as (\"inner\" ", innerType, ", name text)"])
mempty
Decoders.noResult
-- Test decoding
Session.statement ()
$ Statement.preparable
(mconcat ["select (('large', 5), 'test') :: ", outerType])
mempty
( Decoders.singleRow
( Decoders.column
( Decoders.nonNullable
( Decoders.composite
Nothing
outerType
( (,)
<$> Decoders.field
( Decoders.nonNullable
( Decoders.composite
Nothing
innerType
( (,)
<$> Decoders.field (Decoders.nonNullable (Decoders.enum Nothing enumName (Just . id)))
<*> Decoders.field (Decoders.nonNullable Decoders.int4)
)
)
)
<*> Decoders.field (Decoders.nonNullable Decoders.text)
)
)
)
)
)
result `shouldBe` Right (("large", 5 :: Int32), "test")
describe "Arrays of enums" do
it "decodes arrays of named enums from static SQL" \config -> do
enumName <- Scripts.generateSymname
Scripts.onPreparableConnection config \connection -> do
result <- Connection.use connection do
-- Create enum type
Session.statement ()
$ Statement.preparable
(mconcat ["create type ", enumName, " as enum ('first', 'second', 'third')"])
mempty
Decoders.noResult
-- Test array decoding
Session.statement ()
$ Statement.preparable
(mconcat ["select array['first', 'third', 'second'] :: ", enumName, "[]"])
mempty
( Decoders.singleRow
( Decoders.column
( Decoders.nonNullable
( Decoders.array
( Decoders.dimension
replicateM
(Decoders.element (Decoders.nonNullable (Decoders.enum Nothing enumName (Just . id))))
)
)
)
)
)
result `shouldBe` Right ["first", "third", "second"]
it "decodes 2D arrays of named enums" \config -> do
enumName <- Scripts.generateSymname
Scripts.onPreparableConnection config \connection -> do
result <- Connection.use connection do
-- Create enum type
Session.statement ()
$ Statement.preparable
(mconcat ["create type ", enumName, " as enum ('a', 'b', 'c')"])
mempty
Decoders.noResult
-- Test 2D array decoding
Session.statement ()
$ Statement.preparable
(mconcat ["select array[array['a', 'b'], array['c', 'a']] :: ", enumName, "[][]"])
mempty
( Decoders.singleRow
( Decoders.column
( Decoders.nonNullable
( Decoders.array
( Decoders.dimension
replicateM
( Decoders.dimension
replicateM
(Decoders.element (Decoders.nonNullable (Decoders.enum Nothing enumName (Just . id))))
)
)
)
)
)
)
result `shouldBe` Right [["a", "b"], ["c", "a"]]
it "decodes arrays of composites containing enums" \config -> do
enumName <- Scripts.generateSymname
compositeName <- Scripts.generateSymname
Scripts.onPreparableConnection config \connection -> do
result <- Connection.use connection do
-- Create enum type
Session.statement ()
$ Statement.preparable
(mconcat ["create type ", enumName, " as enum ('low', 'high')"])
mempty
Decoders.noResult
-- Create composite type with enum
Session.statement ()
$ Statement.preparable
(mconcat ["create type ", compositeName, " as (priority ", enumName, ", id int4)"])
mempty
Decoders.noResult
-- Test decoding array of composites with enums
Session.statement ()
$ Statement.preparable
(mconcat ["select array[('high', 1), ('low', 2)] :: ", compositeName, "[]"])
mempty
( Decoders.singleRow
( Decoders.column
( Decoders.nonNullable
( Decoders.array
( Decoders.dimension
replicateM
( Decoders.element
( Decoders.nonNullable
( Decoders.composite
Nothing
compositeName
( (,)
<$> Decoders.field (Decoders.nonNullable (Decoders.enum Nothing enumName (Just . id)))
<*> Decoders.field (Decoders.nonNullable Decoders.int4)
)
)
)
)
)
)
)
)
)
result `shouldBe` Right [("high", 1 :: Int32), ("low", 2)]
it "detects attempts to decode non-existent enum types" \config -> do
Scripts.onPreparableConnection config \connection -> do
result <- Connection.use connection do
Session.statement ()
$ Statement.preparable
"select 'value'::text"
mempty
(Decoders.singleRow (Decoders.column (Decoders.nonNullable (Decoders.enum Nothing "nonexistent_enum_type" (Just . id)))))
case result of
Left (Errors.MissingTypesSessionError missingTypes) ->
missingTypes `shouldBe` HashSet.fromList [(Nothing, "nonexistent_enum_type")]
_ ->
expectationFailure ("Unexpected result: " <> show result)
it "detects attempts to decode arrays of non-existent enum types" \config -> do
Scripts.onPreparableConnection config \connection -> do
result <- Connection.use connection do
Session.statement ()
$ Statement.preparable
"select array['a', 'b']::text[]"
mempty
( Decoders.singleRow
( Decoders.column
( Decoders.nonNullable
( Decoders.array
( Decoders.dimension
replicateM
(Decoders.element (Decoders.nonNullable (Decoders.enum Nothing "nonexistent_array_enum" (Just . id))))
)
)
)
)
)
case result of
Left (Errors.MissingTypesSessionError missingTypes) ->
missingTypes `shouldBe` HashSet.fromList [(Nothing, "nonexistent_array_enum")]
_ ->
expectationFailure ("Unexpected result: " <> show result)