packages feed

hasql-1.10: src/library-tests/Sharing/ByUnit/Decoders/DomainSpec.hs

module Sharing.ByUnit.Decoders.DomainSpec (spec) where

import Hasql.Connection qualified as Connection
import Hasql.Decoders qualified as Decoders
import Hasql.Encoders qualified as Encoders
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 "Domain type decoding" do
    describe "Simple scalar domains" do
      it "decodes a domain based on int8 using int8 codec" \config -> do
        domainName <- Scripts.generateSymname
        Scripts.onPreparableConnection config \connection -> do
          result <- Connection.use connection do
            -- Create domain type
            Session.statement ()
              $ Statement.preparable
                (mconcat ["create domain ", domainName, " as int8"])
                mempty
                Decoders.noResult
            -- Test decoding from static value
            Session.statement ()
              $ Statement.preparable
                (mconcat ["select 42 :: ", domainName])
                mempty
                (Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.int8)))
          result `shouldBe` Right (42 :: Int64)

      it "decodes a domain based on text using text codec" \config -> do
        domainName <- Scripts.generateSymname
        Scripts.onPreparableConnection config \connection -> do
          result <- Connection.use connection do
            -- Create domain type
            Session.statement ()
              $ Statement.preparable
                (mconcat ["create domain ", domainName, " as text"])
                mempty
                Decoders.noResult
            -- Test decoding from static value
            Session.statement ()
              $ Statement.preparable
                (mconcat ["select 'hello' :: ", domainName])
                mempty
                (Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.text)))
          result `shouldBe` Right "hello"

      it "decodes a domain based on bool using bool codec" \config -> do
        domainName <- Scripts.generateSymname
        Scripts.onPreparableConnection config \connection -> do
          result <- Connection.use connection do
            -- Create domain type
            Session.statement ()
              $ Statement.preparable
                (mconcat ["create domain ", domainName, " as bool"])
                mempty
                Decoders.noResult
            -- Test decoding from static value
            Session.statement ()
              $ Statement.preparable
                (mconcat ["select true :: ", domainName])
                mempty
                (Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.bool)))
          result `shouldBe` Right True

      it "roundtrips a domain based on numeric" \config -> do
        domainName <- Scripts.generateSymname
        Scripts.onPreparableConnection config \connection -> do
          result <- Connection.use connection do
            -- Create domain type
            Session.statement ()
              $ Statement.preparable
                (mconcat ["create domain ", domainName, " as numeric"])
                mempty
                Decoders.noResult
            -- Test roundtrip
            Session.statement (123.456 :: Scientific)
              $ Statement.preparable
                (mconcat ["select $1 :: ", domainName])
                (Encoders.param (Encoders.nonNullable Encoders.numeric))
                (Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.numeric)))
          result `shouldBe` Right (123.456 :: Scientific)

    describe "Domain with constraints" do
      it "decodes domain values that satisfy constraints" \config -> do
        domainName <- Scripts.generateSymname
        Scripts.onPreparableConnection config \connection -> do
          result <- Connection.use connection do
            -- Create domain type with constraint
            Session.statement ()
              $ Statement.preparable
                (mconcat ["create domain ", domainName, " as int8 check (value > 0)"])
                mempty
                Decoders.noResult
            -- Decode value that satisfies constraint
            Session.statement ()
              $ Statement.preparable
                (mconcat ["select 42 :: ", domainName])
                mempty
                (Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.int8)))
          result `shouldBe` Right (42 :: Int64)

    describe "Domain type cast compatibility for composite usage" do
      it "decodes domain value cast to base type from composite field" \config -> do
        domainName <- Scripts.generateSymname
        compositeName <- Scripts.generateSymname
        Scripts.onPreparableConnection config \connection -> do
          result <- Connection.use connection do
            -- Create domain type
            Session.statement ()
              $ Statement.preparable
                (mconcat ["create domain ", domainName, " as int8"])
                mempty
                Decoders.noResult
            -- Create composite type with domain field
            Session.statement ()
              $ Statement.preparable
                (mconcat ["create type ", compositeName, " as (x ", domainName, ", y bool)"])
                mempty
                Decoders.noResult
            -- Extract and cast domain field to base type for decoding
            Session.statement ()
              $ Statement.preparable
                (mconcat ["select ((42 :: ", domainName, ") :: int8)"])
                mempty
                (Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.int8)))
          result `shouldBe` Right (42 :: Int64)

    describe "Domain type cast compatibility for array usage" do
      it "decodes array cast from domain array to base type array" \config -> do
        domainName <- Scripts.generateSymname
        Scripts.onPreparableConnection config \connection -> do
          result <- Connection.use connection do
            -- Create domain type
            Session.statement ()
              $ Statement.preparable
                (mconcat ["create domain ", domainName, " as int8"])
                mempty
                Decoders.noResult
            -- Decode base type array
            Session.statement ()
              $ Statement.preparable
                "select ARRAY[1,2,3] :: int8[]"
                mempty
                (Decoders.singleRow (Decoders.column (Decoders.nonNullable (Decoders.listArray (Decoders.nonNullable Decoders.int8)))))
          result `shouldBe` Right ([1, 2, 3] :: [Int64])

      it "roundtrips array using base type codec" \config -> do
        domainName <- Scripts.generateSymname
        Scripts.onPreparableConnection config \connection -> do
          result <- Connection.use connection do
            -- Create domain type
            Session.statement ()
              $ Statement.preparable
                (mconcat ["create domain ", domainName, " as text"])
                mempty
                Decoders.noResult
            -- Test roundtrip using base type codec
            Session.statement (["a", "b", "c"] :: [Text])
              $ Statement.preparable
                "select $1 :: text[]"
                ( Encoders.param
                    ( Encoders.nonNullable
                        (Encoders.foldableArray (Encoders.nonNullable Encoders.text))
                    )
                )
                (Decoders.singleRow (Decoders.column (Decoders.nonNullable (Decoders.listArray (Decoders.nonNullable Decoders.text)))))
          result `shouldBe` Right (["a", "b", "c"] :: [Text])

      it "decodes base type array that can work with domain arrays via cast" \config -> do
        domainName <- Scripts.generateSymname
        Scripts.onPreparableConnection config \connection -> do
          result <- Connection.use connection do
            -- Create domain type
            Session.statement ()
              $ Statement.preparable
                (mconcat ["create domain ", domainName, " as int8"])
                mempty
                Decoders.noResult
            -- Demonstrate that base codec works for arrays
            Session.statement ([10, 20, 30] :: [Int64])
              $ Statement.preparable
                "select $1 :: int8[]"
                ( Encoders.param
                    ( Encoders.nonNullable
                        (Encoders.foldableArray (Encoders.nonNullable Encoders.int8))
                    )
                )
                (Decoders.singleRow (Decoders.column (Decoders.nonNullable (Decoders.listArray (Decoders.nonNullable Decoders.int8)))))
          result `shouldBe` Right ([10, 20, 30] :: [Int64])