hasql/tasty/Main.hs

500 lines
26 KiB
Haskell
Raw Normal View History

2015-11-10 21:19:41 +03:00
module Main where
2022-06-20 13:54:54 +03:00
import Contravariant.Extras
2024-04-19 07:38:30 +03:00
import Hasql.Decoders qualified as Decoders
import Hasql.Encoders qualified as Encoders
import Hasql.Session qualified as Session
import Hasql.Statement qualified as Statement
2024-04-20 13:59:11 +03:00
import Hasql.TestingUtils.TestingDsl qualified as Session
2024-04-19 07:38:30 +03:00
import Main.Connection qualified as Connection
2016-01-22 18:43:15 +03:00
import Main.Prelude hiding (assert)
2024-04-19 07:38:30 +03:00
import Main.Statements qualified as Statements
2023-10-16 03:54:25 +03:00
import Test.QuickCheck.Instances ()
2015-11-10 21:19:41 +03:00
import Test.Tasty
2015-12-06 07:06:31 +03:00
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
2022-06-20 13:54:54 +03:00
import Test.Tasty.Runners
2015-11-10 21:19:41 +03:00
2023-10-16 03:54:25 +03:00
main :: IO ()
2015-11-10 21:19:41 +03:00
main =
defaultMain tree
2023-10-16 03:54:25 +03:00
tree :: TestTree
2015-11-10 21:19:41 +03:00
tree =
2023-10-13 02:24:12 +03:00
localOption (NumThreads 1)
$ testGroup
2022-06-20 13:54:54 +03:00
"All tests"
2023-10-13 02:24:12 +03:00
[ testGroup "Roundtrips"
$ let roundtrip encoder decoder input =
let session =
let statement = Statement.Statement "select $1" encoder decoder True
in Session.statement input statement
in unsafePerformIO $ do
x <- Connection.with (Session.run session)
return (Right (Right input) === x)
in [ testProperty "Array"
$ let encoder = Encoders.param (Encoders.nonNullable (Encoders.array (Encoders.dimension foldl' (Encoders.element (Encoders.nonNullable Encoders.int8)))))
decoder = Decoders.singleRow (Decoders.column (Decoders.nonNullable (Decoders.array (Decoders.dimension replicateM (Decoders.element (Decoders.nonNullable Decoders.int8))))))
in roundtrip encoder decoder,
testProperty "2D Array"
$ let encoder = Encoders.param (Encoders.nonNullable (Encoders.array (Encoders.dimension foldl' (Encoders.dimension foldl' (Encoders.element (Encoders.nonNullable Encoders.int8))))))
decoder = Decoders.singleRow (Decoders.column (Decoders.nonNullable (Decoders.array (Decoders.dimension replicateM (Decoders.dimension replicateM (Decoders.element (Decoders.nonNullable Decoders.int8)))))))
in \list -> list /= [] ==> roundtrip encoder decoder (replicate 3 list)
],
testCase "Failed query"
$ let statement =
Statement.Statement "select true where 1 = any ($1) and $2" encoder decoder True
where
encoder =
contrazip2
(Encoders.param (Encoders.nonNullable (Encoders.array (Encoders.dimension foldl' (Encoders.element (Encoders.nonNullable Encoders.int8))))))
(Encoders.param (Encoders.nonNullable (Encoders.text)))
decoder =
fmap (maybe False (const True)) (Decoders.rowMaybe ((Decoders.column . Decoders.nonNullable) Decoders.bool))
session =
Session.statement ([3, 7], "a") statement
in do
x <- Connection.with (Session.run session)
assertBool (show x) $ case x of
Right (Left (Session.QueryError "select true where 1 = any ($1) and $2" ["[3, 7]", "\"a\""] _)) -> True
_ -> False,
testCase "IN simulation"
$ let statement =
Statement.Statement "select true where 1 = any ($1)" encoder decoder True
where
encoder =
Encoders.param (Encoders.nonNullable (Encoders.array (Encoders.dimension foldl' (Encoders.element (Encoders.nonNullable Encoders.int8)))))
decoder =
fmap (maybe False (const True)) (Decoders.rowMaybe ((Decoders.column . Decoders.nonNullable) Decoders.bool))
session =
do
result1 <- Session.statement [1, 2] statement
result2 <- Session.statement [2, 3] statement
return (result1, result2)
in do
x <- Connection.with (Session.run session)
assertEqual (show x) (Right (Right (True, False))) x,
testCase "NOT IN simulation"
$ let statement =
Statement.Statement "select true where 3 <> all ($1)" encoder decoder True
where
encoder =
Encoders.param (Encoders.nonNullable (Encoders.array (Encoders.dimension foldl' (Encoders.element (Encoders.nonNullable Encoders.int8)))))
decoder =
fmap (maybe False (const True)) (Decoders.rowMaybe ((Decoders.column . Decoders.nonNullable) Decoders.bool))
session =
do
result1 <- Session.statement [1, 2] statement
result2 <- Session.statement [2, 3] statement
return (result1, result2)
in do
x <- Connection.with (Session.run session)
assertEqual (show x) (Right (Right (True, False))) x,
testCase "Composite decoding"
$ let statement =
Statement.Statement sql encoder decoder True
where
sql =
2023-10-13 02:24:12 +03:00
"select (1, true)"
encoder =
2023-10-13 02:24:12 +03:00
mempty
decoder =
2023-10-13 02:24:12 +03:00
Decoders.singleRow ((Decoders.column . Decoders.nonNullable) (Decoders.composite ((,) <$> (Decoders.field . Decoders.nonNullable) Decoders.int8 <*> (Decoders.field . Decoders.nonNullable) Decoders.bool)))
session =
Session.statement () statement
in do
2022-06-20 13:54:54 +03:00
x <- Connection.with (Session.run session)
2023-10-13 02:24:12 +03:00
assertEqual (show x) (Right (Right (1, True))) x,
testCase "Complex composite decoding"
$ let statement =
Statement.Statement sql encoder decoder True
where
sql =
"select (1, true) as entity1, ('hello', 3) as entity2"
encoder =
mempty
decoder =
Decoders.singleRow
$ (,)
<$> (Decoders.column . Decoders.nonNullable) entity1
<*> (Decoders.column . Decoders.nonNullable) entity2
where
entity1 =
Decoders.composite
$ (,)
<$> (Decoders.field . Decoders.nonNullable) Decoders.int8
<*> (Decoders.field . Decoders.nonNullable) Decoders.bool
entity2 =
Decoders.composite
$ (,)
<$> (Decoders.field . Decoders.nonNullable) Decoders.text
<*> (Decoders.field . Decoders.nonNullable) Decoders.int8
session =
Session.statement () statement
in do
x <- Connection.with (Session.run session)
assertEqual (show x) (Right (Right ((1, True), ("hello", 3)))) x,
testGroup "unknownEnum"
$ [ testCase "" $ do
res <- Session.runSessionOnLocalDb $ do
2022-06-20 13:54:54 +03:00
let statement =
Statement.Statement sql mempty Decoders.noResult True
where
sql =
"drop type if exists mood"
2024-04-20 13:46:51 +03:00
in Session.statement () statement
2022-06-20 13:54:54 +03:00
let statement =
Statement.Statement sql mempty Decoders.noResult True
where
sql =
"create type mood as enum ('sad', 'ok', 'happy')"
2024-04-20 13:46:51 +03:00
in Session.statement () statement
2022-06-20 13:54:54 +03:00
let statement =
Statement.Statement sql encoder decoder True
where
sql =
2023-10-13 02:24:12 +03:00
"select $1"
2022-06-20 13:54:54 +03:00
decoder =
(Decoders.singleRow ((Decoders.column . Decoders.nonNullable) (Decoders.enum (Just . id))))
encoder =
2023-10-13 02:24:12 +03:00
Encoders.param (Encoders.nonNullable (Encoders.unknownEnum id))
2024-04-20 13:46:51 +03:00
in Session.statement "ok" statement
2023-10-13 02:24:12 +03:00
assertEqual "" (Right "ok") res
],
testCase "Composite encoding" $ do
let value =
(123, 456, 789, "abc")
res <-
let statement =
Statement.Statement sql encoder decoder True
where
sql =
"select $1 :: pg_enum"
encoder =
Encoders.param
. Encoders.nonNullable
. Encoders.composite
. mconcat
$ [ contramap (\(a, _, _, _) -> a) . Encoders.field . Encoders.nonNullable $ Encoders.oid,
contramap (\(_, a, _, _) -> a) . Encoders.field . Encoders.nonNullable $ Encoders.oid,
contramap (\(_, _, a, _) -> a) . Encoders.field . Encoders.nonNullable $ Encoders.float4,
contramap (\(_, _, _, a) -> a) . Encoders.field . Encoders.nonNullable $ Encoders.name
]
decoder =
Decoders.singleRow
$ (Decoders.column . Decoders.nonNullable . Decoders.composite)
( (,,,)
<$> (Decoders.field . Decoders.nonNullable) Decoders.int4
<*> (Decoders.field . Decoders.nonNullable) Decoders.int4
<*> (Decoders.field . Decoders.nonNullable) Decoders.float4
<*> (Decoders.field . Decoders.nonNullable) Decoders.text
)
in Connection.with $ Session.run $ Session.statement value statement
assertEqual "" (Right (Right value)) res,
testCase "Empty array"
$ let io =
do
x <- Connection.with (Session.run session)
assertEqual (show x) (Right (Right [])) x
where
session =
Session.statement () statement
where
statement =
Statement.Statement sql encoder decoder True
where
sql =
"select array[]::int8[]"
encoder =
mempty
decoder =
Decoders.singleRow ((Decoders.column . Decoders.nonNullable) (Decoders.array (Decoders.dimension replicateM (Decoders.element (Decoders.nonNullable Decoders.int8)))))
in io,
testCase "Failing prepared statements"
$ let io =
Connection.with (Session.run session)
>>= (assertBool <$> show <*> resultTest)
where
resultTest =
\case
Right (Left (Session.QueryError _ _ (Session.ResultError (Session.ServerError "26000" _ _ _ _)))) -> False
_ -> True
session =
catchError session (const (pure ())) *> session
where
session =
Session.statement () statement
where
statement =
Statement.Statement sql encoder decoder True
where
sql =
"absurd"
encoder =
mempty
decoder =
Decoders.noResult
in io,
testCase "Prepared statements after error"
$ let io =
Connection.with (Session.run session)
>>= \x -> assertBool (show x) (either (const False) isRight x)
where
session =
try *> fail *> try
where
try =
Session.statement 1 statement
where
statement =
Statement.Statement sql encoder decoder True
where
sql =
"select $1 :: int8"
encoder =
Encoders.param (Encoders.nonNullable (Encoders.int8))
decoder =
Decoders.singleRow $ (Decoders.column . Decoders.nonNullable) Decoders.int8
fail =
catchError (Session.sql "absurd") (const (pure ()))
in io,
testCase "\"in progress after error\" bugfix"
$ let sumStatement :: Statement.Statement (Int64, Int64) Int64
sumStatement =
Statement.Statement sql encoder decoder True
where
sql =
"select ($1 + $2)"
encoder =
contramap fst (Encoders.param (Encoders.nonNullable (Encoders.int8)))
<> contramap snd (Encoders.param (Encoders.nonNullable (Encoders.int8)))
decoder =
Decoders.singleRow ((Decoders.column . Decoders.nonNullable) Decoders.int8)
sumSession :: Session.Session Int64
sumSession =
Session.sql "begin" *> Session.statement (1, 1) sumStatement <* Session.sql "end"
errorSession :: Session.Session ()
errorSession =
Session.sql "asldfjsldk"
io =
Connection.with $ \c -> do
Session.run errorSession c
Session.run sumSession c
in io >>= \x -> assertBool (show x) (either (const False) isRight x),
testCase "\"another command is already in progress\" bugfix"
$ let sumStatement :: Statement.Statement (Int64, Int64) Int64
sumStatement =
Statement.Statement sql encoder decoder True
where
sql =
"select ($1 + $2)"
encoder =
contramap fst (Encoders.param (Encoders.nonNullable (Encoders.int8)))
<> contramap snd (Encoders.param (Encoders.nonNullable (Encoders.int8)))
decoder =
Decoders.singleRow ((Decoders.column . Decoders.nonNullable) Decoders.int8)
session :: Session.Session Int64
session =
do
Session.sql "begin;"
s <- Session.statement (1, 1) sumStatement
Session.sql "end;"
return s
in Session.runSessionOnLocalDb session >>= \x -> assertEqual (show x) (Right 2) x,
2023-10-13 02:24:12 +03:00
testCase "Executing the same query twice"
$ pure (),
testCase "Interval Encoding"
$ let actualIO =
Session.runSessionOnLocalDb $ do
2023-10-13 02:24:12 +03:00
let statement =
Statement.Statement sql encoder decoder True
where
sql =
"select $1 = interval '10 seconds'"
decoder =
(Decoders.singleRow ((Decoders.column . Decoders.nonNullable) (Decoders.bool)))
encoder =
Encoders.param (Encoders.nonNullable (Encoders.interval))
2024-04-20 13:46:51 +03:00
in Session.statement (10 :: DiffTime) statement
2023-10-13 02:24:12 +03:00
in actualIO >>= \x -> assertEqual (show x) (Right True) x,
testCase "Interval Decoding"
$ let actualIO =
Session.runSessionOnLocalDb $ do
2023-10-13 02:24:12 +03:00
let statement =
Statement.Statement sql encoder decoder True
where
sql =
"select interval '10 seconds'"
decoder =
(Decoders.singleRow ((Decoders.column . Decoders.nonNullable) (Decoders.interval)))
encoder =
Encoders.noParams
2024-04-20 13:46:51 +03:00
in Session.statement () statement
2023-10-13 02:24:12 +03:00
in actualIO >>= \x -> assertEqual (show x) (Right (10 :: DiffTime)) x,
testCase "Interval Encoding/Decoding"
$ let actualIO =
Session.runSessionOnLocalDb $ do
2023-10-13 02:24:12 +03:00
let statement =
Statement.Statement sql encoder decoder True
where
sql =
"select $1"
decoder =
(Decoders.singleRow ((Decoders.column . Decoders.nonNullable) (Decoders.interval)))
encoder =
Encoders.param (Encoders.nonNullable (Encoders.interval))
2024-04-20 13:46:51 +03:00
in Session.statement (10 :: DiffTime) statement
2023-10-13 02:24:12 +03:00
in actualIO >>= \x -> assertEqual (show x) (Right (10 :: DiffTime)) x,
testCase "Unknown"
$ let actualIO =
Session.runSessionOnLocalDb $ do
2023-10-13 02:24:12 +03:00
let statement =
Statement.Statement sql mempty Decoders.noResult True
where
sql =
"drop type if exists mood"
2024-04-20 13:46:51 +03:00
in Session.statement () statement
2023-10-13 02:24:12 +03:00
let statement =
Statement.Statement sql mempty Decoders.noResult True
where
sql =
"create type mood as enum ('sad', 'ok', 'happy')"
2024-04-20 13:46:51 +03:00
in Session.statement () statement
2023-10-13 02:24:12 +03:00
let statement =
Statement.Statement sql encoder decoder True
where
sql =
"select $1 = ('ok' :: mood)"
decoder =
(Decoders.singleRow ((Decoders.column . Decoders.nonNullable) (Decoders.bool)))
encoder =
Encoders.param (Encoders.nonNullable (Encoders.unknown))
2024-04-20 13:46:51 +03:00
in Session.statement "ok" statement
2023-10-13 02:24:12 +03:00
in actualIO >>= assertEqual "" (Right True),
testCase "Textual Unknown"
$ let actualIO =
Session.runSessionOnLocalDb $ do
2023-10-13 02:24:12 +03:00
let statement =
Statement.Statement sql mempty Decoders.noResult True
where
sql =
"create or replace function overloaded(a int, b int) returns int as $$ select a + b $$ language sql;"
2024-04-20 13:46:51 +03:00
in Session.statement () statement
2023-10-13 02:24:12 +03:00
let statement =
Statement.Statement sql mempty Decoders.noResult True
where
sql =
"create or replace function overloaded(a text, b text, c text) returns text as $$ select a || b || c $$ language sql;"
2024-04-20 13:46:51 +03:00
in Session.statement () statement
2023-10-13 02:24:12 +03:00
let statement =
Statement.Statement sql encoder decoder True
where
sql =
"select overloaded($1, $2) || overloaded($3, $4, $5)"
decoder =
(Decoders.singleRow ((Decoders.column . Decoders.nonNullable) (Decoders.text)))
encoder =
contramany (Encoders.param (Encoders.nonNullable (Encoders.unknown)))
2024-04-20 13:46:51 +03:00
in Session.statement ["1", "2", "4", "5", "6"] statement
2023-10-13 02:24:12 +03:00
in actualIO >>= assertEqual "" (Right "3456"),
testCase "Enum"
$ let actualIO =
Session.runSessionOnLocalDb $ do
2023-10-13 02:24:12 +03:00
let statement =
Statement.Statement sql mempty Decoders.noResult True
where
sql =
"drop type if exists mood"
2024-04-20 13:46:51 +03:00
in Session.statement () statement
2023-10-13 02:24:12 +03:00
let statement =
Statement.Statement sql mempty Decoders.noResult True
where
sql =
"create type mood as enum ('sad', 'ok', 'happy')"
2024-04-20 13:46:51 +03:00
in Session.statement () statement
2023-10-13 02:24:12 +03:00
let statement =
Statement.Statement sql encoder decoder True
where
sql =
"select ($1 :: mood)"
decoder =
(Decoders.singleRow ((Decoders.column . Decoders.nonNullable) (Decoders.enum (Just . id))))
encoder =
Encoders.param (Encoders.nonNullable ((Encoders.enum id)))
2024-04-20 13:46:51 +03:00
in Session.statement "ok" statement
2023-10-13 02:24:12 +03:00
in actualIO >>= assertEqual "" (Right "ok"),
testCase "The same prepared statement used on different types"
$ let actualIO =
Session.runSessionOnLocalDb $ do
2023-10-13 02:24:12 +03:00
let effect1 =
2024-04-20 13:46:51 +03:00
Session.statement "ok" statement
2023-10-13 02:24:12 +03:00
where
statement =
Statement.Statement sql encoder decoder True
where
sql =
"select $1"
encoder =
Encoders.param (Encoders.nonNullable (Encoders.text))
decoder =
(Decoders.singleRow ((Decoders.column . Decoders.nonNullable) (Decoders.text)))
effect2 =
2024-04-20 13:46:51 +03:00
Session.statement 1 statement
2023-10-13 02:24:12 +03:00
where
statement =
Statement.Statement sql encoder decoder True
where
sql =
"select $1"
encoder =
Encoders.param (Encoders.nonNullable (Encoders.int8))
decoder =
(Decoders.singleRow ((Decoders.column . Decoders.nonNullable) Decoders.int8))
in (,) <$> effect1 <*> effect2
in actualIO >>= assertEqual "" (Right ("ok", 1)),
testCase "Affected rows counting"
$ replicateM_ 13
$ let actualIO =
Session.runSessionOnLocalDb $ do
2022-06-20 13:54:54 +03:00
dropTable
createTable
replicateM_ 100 insertRow
deleteRows <* dropTable
2015-11-15 12:13:45 +03:00
where
2022-06-20 13:54:54 +03:00
dropTable =
2024-04-20 13:46:51 +03:00
Session.statement ()
2023-10-13 02:24:12 +03:00
$ Statements.plain
$ "drop table if exists a"
2022-06-20 13:54:54 +03:00
createTable =
2024-04-20 13:46:51 +03:00
Session.statement ()
2023-10-13 02:24:12 +03:00
$ Statements.plain
$ "create table a (id bigserial not null, name varchar not null, primary key (id))"
2022-06-20 13:54:54 +03:00
insertRow =
2024-04-20 13:46:51 +03:00
Session.statement ()
2023-10-13 02:24:12 +03:00
$ Statements.plain
$ "insert into a (name) values ('a')"
2022-06-20 13:54:54 +03:00
deleteRows =
2024-04-20 13:46:51 +03:00
Session.statement () $ Statement.Statement sql mempty decoder False
2022-06-20 13:54:54 +03:00
where
sql =
"delete from a"
decoder =
Decoders.rowsAffected
in actualIO >>= assertEqual "" (Right 100),
2023-10-13 02:24:12 +03:00
testCase "Result of an auto-incremented column"
$ let actualIO =
Session.runSessionOnLocalDb $ do
2024-04-20 13:46:51 +03:00
Session.statement () $ Statements.plain $ "drop table if exists a"
Session.statement () $ Statements.plain $ "create table a (id serial not null, v char not null, primary key (id))"
id1 <- Session.statement () $ Statement.Statement "insert into a (v) values ('a') returning id" mempty (Decoders.singleRow ((Decoders.column . Decoders.nonNullable) Decoders.int4)) False
id2 <- Session.statement () $ Statement.Statement "insert into a (v) values ('b') returning id" mempty (Decoders.singleRow ((Decoders.column . Decoders.nonNullable) Decoders.int4)) False
Session.statement () $ Statements.plain $ "drop table if exists a"
2023-10-13 02:24:12 +03:00
pure (id1, id2)
in assertEqual "" (Right (1, 2)) =<< actualIO,
testCase "List decoding"
$ let actualIO =
Session.runSessionOnLocalDb $ Session.statement () $ Statements.selectList
2023-10-13 02:24:12 +03:00
in assertEqual "" (Right [(1, 2), (3, 4), (5, 6)]) =<< actualIO
2022-06-20 13:54:54 +03:00
]