module Test.Expr.Cursor
( cursorTests
)
where
import qualified Control.Exception.Safe as ExSafe
import qualified Control.Monad.IO.Class as MIO
import qualified Data.ByteString.Char8 as B8
import qualified Data.Int as Int
import qualified Data.List.NonEmpty as NE
import Hedgehog ((===))
import qualified Hedgehog as HH
import qualified Orville.PostgreSQL as Orville
import qualified Orville.PostgreSQL.Execution as Execution
import qualified Orville.PostgreSQL.Expr as Expr
import qualified Orville.PostgreSQL.Raw.Connection as Conn
import qualified Orville.PostgreSQL.Raw.RawSql as RawSql
import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue
import Test.Expr.TestSchema (FooBar, assertEqualFooBarRows, findAllFooBars, mkFooBar, withFooBarData)
import qualified Test.Property as Property
cursorTests :: Orville.ConnectionPool -> Property.Group
cursorTests pool =
Property.group
"Expr - Cursor"
[ prop_cursorInTransaction pool
, prop_cursorOutsideTransactionWithHold pool
, prop_cursorCloseAll pool
, prop_cursorMove pool
, prop_cursorNoScroll pool
, prop_cursorFetchAll pool
, prop_cursorFetchRowCount pool
, prop_cursorFetchForward pool
, prop_cursorFetchForwardCount pool
, prop_cursorFetchForwardAll pool
, prop_cursorFetchBackward pool
, prop_cursorFetchBackwardCount pool
, prop_cursorFetchBackwardAll pool
, prop_cursorFetchFirstLast pool
, prop_cursorFetchNextPrior pool
, prop_cursorFetchAbsolute pool
, prop_cursorFetchRelative pool
]
prop_cursorInTransaction :: Property.NamedDBProperty
prop_cursorInTransaction =
Property.singletonNamedDBProperty "In transaction" $ \pool -> do
result <-
withFooBarData pool (NE.fromList [row 1, row 2]) $ \connection ->
withTestTransaction connection $
withTestCursor connection Nothing Nothing findAllFooBars $ \cursorName -> do
result <- RawSql.execute connection $ Expr.fetch Nothing cursorName
Execution.readRows result
assertEqualFooBarRows result [row 1]
prop_cursorOutsideTransactionWithHold :: Property.NamedDBProperty
prop_cursorOutsideTransactionWithHold =
Property.singletonNamedDBProperty "Outside transaction (with hold)" $ \pool -> do
result <-
withFooBarData pool (NE.fromList [row 1, row 2]) $ \connection ->
withTestCursor connection Nothing (Just Expr.withHold) findAllFooBars $ \cursorName -> do
result <- RawSql.execute connection $ Expr.fetch Nothing cursorName
Execution.readRows result
assertEqualFooBarRows result [row 1]
prop_cursorCloseAll :: Property.NamedDBProperty
prop_cursorCloseAll =
Property.singletonNamedDBProperty "Close all cursors" $ \pool -> do
MIO.liftIO . Conn.withPoolConnection pool $ \connection -> do
let
cursorName :: Expr.CursorName
cursorName = Expr.fromIdentifier $ Expr.identifier "testcursor"
declare =
RawSql.executeVoid connection
. Expr.declare cursorName Nothing (Just Expr.withHold)
$ RawSql.unsafeSqlExpression "SELECT 1"
close =
RawSql.executeVoid connection $ Expr.close (Left Expr.allCursors)
-- As long as close doesn't raise an exception, the test passes
ExSafe.bracket_ declare close (pure ())
prop_cursorMove :: Property.NamedDBProperty
prop_cursorMove =
Property.singletonNamedDBProperty "Move" $ \pool -> do
result <-
withFooBarData pool (NE.fromList [row 1, row 2, row 3]) $ \connection ->
withTestCursor connection Nothing (Just Expr.withHold) findAllFooBars $ \cursorName -> do
RawSql.executeVoid connection $ Expr.move (Just $ Expr.rowCount 2) cursorName
result <- RawSql.execute connection $ Expr.fetch Nothing cursorName
Execution.readRows result
assertEqualFooBarRows result [row 3]
prop_cursorNoScroll :: Property.NamedDBProperty
prop_cursorNoScroll =
Property.singletonNamedDBProperty "Move" $ \pool -> do
scrollBackResult <-
withFooBarData pool (NE.fromList [row 1, row 2]) $ \connection ->
withTestCursor connection (Just Expr.noScroll) (Just Expr.withHold) findAllFooBars $ \cursorName -> do
RawSql.executeVoid connection $ Expr.move (Just Expr.next) cursorName
ExSafe.try $ RawSql.executeVoid connection $ Expr.move (Just Expr.prior) cursorName
case scrollBackResult of
Right () -> do
HH.footnote "Expected 'executeVoid' to return failure, but it did not"
HH.failure
Left err ->
-- Expected that the execute failed because we tried to scroll backward
-- on a non-scrollable cursor
Conn.sqlExecutionErrorSqlState err === Just (B8.pack "55000")
prop_cursorFetchAll :: Property.NamedDBProperty
prop_cursorFetchAll =
Property.singletonNamedDBProperty "Fetch all" $ \pool -> do
[first] <-
runFetchDirectionsOnData
pool
Nothing
(NE.fromList [row 1, row 2])
[Expr.fetchAll]
assertEqualFooBarRows first [row 1, row 2]
prop_cursorFetchRowCount :: Property.NamedDBProperty
prop_cursorFetchRowCount =
Property.singletonNamedDBProperty "Fetch row count" $ \pool -> do
[first, second] <-
runFetchDirectionsOnData
pool
Nothing
(NE.fromList [row 1, row 2, row 3])
[Expr.rowCount 2, Expr.rowCount 2]
assertEqualFooBarRows first [row 1, row 2]
assertEqualFooBarRows second [row 3]
prop_cursorFetchForward :: Property.NamedDBProperty
prop_cursorFetchForward =
Property.singletonNamedDBProperty "Fetch forward" $ \pool -> do
[first, second] <-
runFetchDirectionsOnData
pool
Nothing
(NE.fromList [row 1, row 2])
[Expr.forward, Expr.forward]
assertEqualFooBarRows first [row 1]
assertEqualFooBarRows second [row 2]
prop_cursorFetchForwardCount :: Property.NamedDBProperty
prop_cursorFetchForwardCount =
Property.singletonNamedDBProperty "Fetch forward count" $ \pool -> do
[first, second] <-
runFetchDirectionsOnData
pool
Nothing
(NE.fromList [row 1, row 2, row 3])
[Expr.forwardCount 2, Expr.forwardCount 2]
assertEqualFooBarRows first [row 1, row 2]
assertEqualFooBarRows second [row 3]
prop_cursorFetchForwardAll :: Property.NamedDBProperty
prop_cursorFetchForwardAll =
Property.singletonNamedDBProperty "Fetch forward all" $ \pool -> do
[first] <-
runFetchDirectionsOnData
pool
Nothing
(NE.fromList [row 1, row 2])
[Expr.forwardAll]
assertEqualFooBarRows first [row 1, row 2]
prop_cursorFetchBackward :: Property.NamedDBProperty
prop_cursorFetchBackward =
Property.singletonNamedDBProperty "Fetch backward" $ \pool -> do
[first, second] <-
runFetchDirectionsOnData
pool
(Just Expr.scroll)
(NE.fromList [row 1, row 2])
[Expr.forwardCount 2, Expr.backward]
assertEqualFooBarRows first [row 1, row 2]
assertEqualFooBarRows second [row 1]
prop_cursorFetchBackwardCount :: Property.NamedDBProperty
prop_cursorFetchBackwardCount =
Property.singletonNamedDBProperty "Fetch backward count" $ \pool -> do
[first, second] <-
runFetchDirectionsOnData
pool
(Just Expr.scroll)
(NE.fromList [row 1, row 2, row 3])
[Expr.forwardCount 3, Expr.backwardCount 2]
assertEqualFooBarRows first [row 1, row 2, row 3]
assertEqualFooBarRows second [row 2, row 1]
prop_cursorFetchBackwardAll :: Property.NamedDBProperty
prop_cursorFetchBackwardAll =
Property.singletonNamedDBProperty "Fetch backward all" $ \pool -> do
[first, second] <-
runFetchDirectionsOnData
pool
(Just Expr.scroll)
(NE.fromList [row 1, row 2, row 3])
[Expr.forwardCount 4, Expr.backwardAll]
assertEqualFooBarRows first [row 1, row 2, row 3]
assertEqualFooBarRows second [row 3, row 2, row 1]
prop_cursorFetchFirstLast :: Property.NamedDBProperty
prop_cursorFetchFirstLast =
Property.singletonNamedDBProperty "Fetch first/last" $ \pool -> do
[first, second] <-
runFetchDirectionsOnData
pool
Nothing
(NE.fromList [row 1, row 2, row 3])
[Expr.first, Expr.last]
assertEqualFooBarRows first [row 1]
assertEqualFooBarRows second [row 3]
prop_cursorFetchNextPrior :: Property.NamedDBProperty
prop_cursorFetchNextPrior =
Property.singletonNamedDBProperty "Fetch next/prior" $ \pool -> do
[first, second, third] <-
runFetchDirectionsOnData
pool
(Just Expr.scroll)
(NE.fromList [row 1, row 2, row 3])
[Expr.next, Expr.next, Expr.prior]
assertEqualFooBarRows first [row 1]
assertEqualFooBarRows second [row 2]
assertEqualFooBarRows third [row 1]
prop_cursorFetchAbsolute :: Property.NamedDBProperty
prop_cursorFetchAbsolute =
Property.singletonNamedDBProperty "Fetch absolute" $ \pool -> do
[first, second] <-
runFetchDirectionsOnData
pool
(Just Expr.scroll)
(NE.fromList [row 1, row 2, row 3])
[Expr.absolute 3, Expr.absolute (-1)]
assertEqualFooBarRows first [row 3]
assertEqualFooBarRows second [row 3]
prop_cursorFetchRelative :: Property.NamedDBProperty
prop_cursorFetchRelative =
Property.singletonNamedDBProperty "Fetch relative" $ \pool -> do
[first, second, third, fourth] <-
runFetchDirectionsOnData
pool
(Just Expr.scroll)
(NE.fromList [row 1, row 2, row 3])
[Expr.relative 1, Expr.relative 2, Expr.relative (-1), Expr.relative 0]
assertEqualFooBarRows first [row 1]
assertEqualFooBarRows second [row 3]
assertEqualFooBarRows third [row 2]
assertEqualFooBarRows fourth [row 2]
row :: Int.Int32 -> FooBar
row n = mkFooBar n ("row " <> show n)
runFetchDirectionsOnData ::
Orville.ConnectionPool ->
Maybe Expr.ScrollExpr ->
NE.NonEmpty FooBar ->
[Expr.CursorDirection] ->
HH.PropertyT IO [[[(Maybe B8.ByteString, SqlValue.SqlValue)]]]
runFetchDirectionsOnData pool scroll fooBars directions =
withFooBarData pool fooBars $ \connection ->
withTestCursor connection scroll (Just Expr.withHold) findAllFooBars $ \cursorName ->
let
runDirection direction = do
result <- RawSql.execute connection $ Expr.fetch (Just direction) cursorName
Execution.readRows result
in
traverse runDirection directions
withTestCursor ::
Orville.Connection ->
Maybe Expr.ScrollExpr ->
Maybe Expr.HoldExpr ->
Expr.QueryExpr ->
(Expr.CursorName -> IO a) ->
IO a
withTestCursor connection scroll hold query action =
let
cursorName :: Expr.CursorName
cursorName = Expr.fromIdentifier $ Expr.identifier "testcursor"
declare =
RawSql.executeVoid connection $
Expr.declare cursorName scroll hold query
close =
RawSql.executeVoid connection $ Expr.close (Right cursorName)
in
ExSafe.bracket_ declare close (action cursorName)
withTestTransaction :: Orville.Connection -> IO a -> IO a
withTestTransaction connection action =
let
begin =
RawSql.executeVoid connection $ Expr.beginTransaction Nothing
commit =
RawSql.executeVoid connection $ Expr.commit
in
ExSafe.bracket_ begin commit action