pqi-conformance-1.0.17.0: src/library/Pqi/Conformance/Operation/Getvalue/ManyRows.hs
-- | Coverage for reading every cell of a large result: 200,000 single-column
-- rows, fetched through 'Pqi.execParams' and scanned row by row with
-- 'Pqi.getisnull', 'Pqi.getlength' and 'Pqi.getvalue'.
--
-- The divergence: @pqi-native@'s 'Pqi.Native.Types.mkResult' kept the rows
-- as a list and reached the requested row with @drop@ from its head on
-- every lookup, so scanning a result through the public accessors was
-- quadratic in its rows. @libpq@ indexes an array, so each lookup is
-- constant time. The values themselves came back intact, only slowly: the
-- issue measured 3.9 s to scan 40,000 rows against 17 ms with @libpq@.
--
-- Where it surfaced: @pqi-native@ issue #15.
--
-- Why this observation: the divergence is in cost, not in any returned
-- value, so the scan runs under a 'deadline' and the observation records
-- whether it finished. The reference scans these rows in about 0.1 s, and
-- the quadratic candidate would need over a minute, so the deadline sits
-- far from both. Every cell is checked as well, so that a fix cannot pass
-- by returning the wrong one.
module Pqi.Conformance.Operation.Getvalue.ManyRows
( spec,
)
where
import qualified Data.ByteString as ByteString
import qualified Data.ByteString.Char8 as ByteString.Char8
import qualified Pqi
import Pqi.Conformance.Harness
import Pqi.Conformance.Prelude
import System.Timeout (timeout)
import Test.Hspec
rowCount :: Int32
rowCount = 200_000
-- | How long the scan may take, in microseconds.
deadline :: Int
deadline = 5_000_000
spec :: Pqi.Adapter -> SpecWith ByteString
spec adapter =
describe "getvalue" do
describe "many rows" do
it "scans 200,000 rows within the deadline" \conninfo ->
differential adapter conninfo scenario
-- | Fetch the rows, then report the result status, the row count, and
-- whether every cell checked out within the deadline (@Nothing@ when the
-- deadline passed).
scenario :: Pqi.Connection -> IO (Maybe (Pqi.ExecStatus, Int32, Maybe Bool))
scenario connection = do
result <-
Pqi.execParams
connection
"select i :: int4 from generate_series (1, $1 :: int4) as i"
[Just (23, ByteString.Char8.pack (show rowCount), Pqi.Text)]
Pqi.Binary
for result \r -> do
status <- Pqi.resultStatus r
count <- Pqi.ntuples r
scanned <- timeout deadline (scan r count)
pure (status, count, scanned)
where
scan r count = go 0
where
go row
| row >= count = pure True
| otherwise = do
isNull <- Pqi.getisnull r row 0
size <- Pqi.getlength r row 0
value <- Pqi.getvalue r row 0
let decoded = fmap (ByteString.foldl' (\n byte -> n * 256 + fromIntegral byte) 0) value
if not isNull && size == 4 && decoded == Just (row + 1)
then go (row + 1)
else pure False