packages feed

hasql-1.9.3.2: hspec/Hasql/MiscSpec.hs

module Hasql.MiscSpec (spec) where

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 Hasql.TestingKit.Testcontainers qualified as Testcontainers
import Test.Hspec
import Prelude

spec :: Spec
spec = aroundAll Testcontainers.withConnection do
  describe "Miscellaneous Tests" do
    describe "Interval types" do
      it "encodes intervals correctly" \connection -> do
        let statement =
              Statement.Statement
                "select $1 = interval '10 seconds'"
                (Encoders.param (Encoders.nonNullable Encoders.interval))
                (Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.bool)))
                True
        result <- Session.run (Session.statement (10 :: DiffTime) statement) connection
        result `shouldBe` Right True

      it "decodes intervals correctly" \connection -> do
        let statement =
              Statement.Statement
                "select interval '10 seconds'"
                Encoders.noParams
                (Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.interval)))
                True
        result <- Session.run (Session.statement () statement) connection
        result `shouldBe` Right (10 :: DiffTime)

      it "roundtrips intervals correctly" \connection -> do
        let statement =
              Statement.Statement
                "select $1"
                (Encoders.param (Encoders.nonNullable Encoders.interval))
                (Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.interval)))
                True
        result <- Session.run (Session.statement (10 :: DiffTime) statement) connection
        result `shouldBe` Right (10 :: DiffTime)

    describe "Unknown types" do
      it "handles unknown type encoding" \connection -> do
        -- First create the enum type
        let dropStatement = Statement.Statement "drop type if exists mood" mempty Decoders.noResult True
        let createStatement = Statement.Statement "create type mood as enum ('sad', 'ok', 'happy')" mempty Decoders.noResult True
        let testStatement =
              Statement.Statement
                "select $1 = ('ok' :: mood)"
                (Encoders.param (Encoders.nonNullable Encoders.unknown))
                (Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.bool)))
                True

        result <-
          Session.run
            ( do
                Session.statement () dropStatement
                Session.statement () createStatement
                Session.statement "ok" testStatement
            )
            connection
        result `shouldBe` Right True

    describe "Transaction-like operations" do
      it "handles in progress after error scenario" \connection -> do
        let sumStatement =
              Statement.Statement
                "select ($1 + $2)"
                ( contramap fst (Encoders.param (Encoders.nonNullable Encoders.int8))
                    <> contramap snd (Encoders.param (Encoders.nonNullable Encoders.int8))
                )
                (Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.int8)))
                True

        result <-
          Session.run
            ( do
                Session.sql "begin;"
                s <- Session.statement (1 :: Int64, 1 :: Int64) sumStatement
                Session.sql "end;"
                return s
            )
            connection
        result `shouldBe` Right (2 :: Int64)

      it "recovers properly after query errors" \connection -> do
        let tryStatement =
              Statement.Statement
                "select $1 :: int8"
                (Encoders.param (Encoders.nonNullable Encoders.int8))
                (Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.int8)))
                True

        result <-
          Session.run
            ( do
                -- First successful query
                _ <- Session.statement (1 :: Int64) tryStatement
                -- This should fail but connection should remain usable
                _ <- catchError (Session.sql "absurd") (const (pure ()))
                -- Second successful query
                Session.statement (1 :: Int64) tryStatement
            )
            connection

        result `shouldSatisfy` isRight
  where
    isRight (Right _) = True
    isRight _ = False