packages feed

wireform-parquet-0.1.0.0: test/DecimalCapacityRegression.hs

{-# LANGUAGE OverloadedStrings #-}

module DecimalCapacityRegression (tests) where

import Control.Exception (evaluate)
import Control.Monad (forM_, unless)
import Data.ByteString qualified as BS
import Data.Either (isRight)
import Data.Int (Int32)
import Data.Vector qualified as V
import GHC.Conc (getAllocationCounter)
import Hedgehog qualified as H
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range
import Parquet.Footer qualified as F
import Parquet.Types qualified as P
import Thrift.Encode qualified as T
import Thrift.Value qualified as TV
import Thrift.Wire (ThriftType (..))


-- Raw field numbers make these fixtures independent of the footer encoder.
metadata :: Int32 -> Maybe Int32 -> Bool -> Int32 -> TV.Value
metadata precision width modern physical =
  let decimal = TV.Struct (V.fromList [(1, TV.I32 0), (2, TV.I32 precision)])
      leaf = TV.Struct (V.fromList ([(1, TV.I32 physical), (3, TV.I32 0), (4, TV.String "d"), (6, TV.I32 5), (7, TV.I32 0), (8, TV.I32 precision)] ++ maybe [] (\n -> [(2, TV.I32 n)]) width ++ if modern then [(10, TV.Struct (V.singleton (5, decimal)))] else []))
      root = TV.Struct (V.fromList [(4, TV.String "schema"), (5, TV.I32 1)])
  in TV.Struct (V.fromList [(1, TV.I32 1), (2, TV.List TT_STRUCT (V.fromList [root, leaf])), (3, TV.I64 0), (4, TV.List TT_STRUCT V.empty)])


accepted :: Int32 -> Int32 -> Bool -> Bool
accepted precision width modern = isRight (F.readFooterRaw (T.encodeCompact (metadata precision (Just width) modern 7)))


compatible :: Int32 -> Int32 -> Bool
compatible precision width =
  P.hasCompatiblePhysicalType
    P.SchemaElement
      { P.seName = "d"
      , P.seRepetition = Just P.Required
      , P.seType = Just P.PTFixedLenByteArray
      , P.seTypeLength = Just width
      , P.seNumChildren = Nothing
      , P.seConvertedType = Just P.CTDecimal
      , P.seLogicalType = Just (P.LTDecimal precision 0)
      , P.seFieldId = Nothing
      }


exactCapacity :: Int -> Int
exactCapacity width = search 0 (8 * width)
  where
    limit = (2 :: Integer) ^ (8 * width - 1)
    search low high
      | high - low <= 1 = low
      | (10 :: Integer) ^ middle <= limit = search middle high
      | otherwise = search low middle
      where
        middle = (low + high) `quot` 2


check :: String -> Bool -> IO ()
check name ok = unless ok (fail ("DECIMAL capacity regression: " ++ name))


tests :: IO ()
tests = do
  forM_ [1 .. 512] $ \width -> do
    let capacity = fromIntegral (exactCapacity width)
        physicalWidth = fromIntegral width
    forM_ [False, True] $ \modern -> do
      check "exact capacity is accepted" (accepted capacity physicalWidth modern)
      check "one digit past capacity is rejected" (not (accepted (capacity + 1) physicalWidth modern))
    check "physical compatibility accepts exact capacity" (compatible capacity physicalWidth)
    check "physical compatibility rejects excess precision" (not (compatible (capacity + 1) physicalWidth))
  forM_ [(77, 1), (77, 32), (80, 33), (1000, 33), (maxBound, 32)] $ \(precision, width) -> do
    check "large declared precision cannot bypass fixed capacity" (not (accepted precision width True) && not (accepted precision width False) && not (compatible precision width))
  forM_ [(79, 33), (1000, 416), (maxBound, maxBound), (maxBound, 1000000000), (9, 536870913)] $ \(precision, width) -> do
    check "large valid metadata is accepted without Arrow's76-digit cap" (accepted precision width True && accepted precision width False && compatible precision width)
  forM_ [(0, 1), (-1, 1), (1, 0), (1, -1)] $ \(precision, width) ->
    check "nonpositive precision/width is invalid" (not (accepted precision width True) && not (compatible precision width))
  forM_ [77, 1000, maxBound] $ \precision -> do
    check "BYTE_ARRAY precision remains unbounded by its storage type" (isRight (F.readFooterRaw (T.encodeCompact (metadata precision Nothing True 6))))
    check "legacy BYTE_ARRAY precision remains unbounded" (isRight (F.readFooterRaw (T.encodeCompact (metadata precision Nothing False 6))))
  passed <- H.check $ H.withTests 1000 $ H.property $ do
    width <- H.forAll (Gen.int (Range.linear 1 4096))
    precision <- H.forAll (Gen.int (Range.linear 1 10000))
    let expected = (10 :: Integer) ^ precision <= (2 :: Integer) ^ (8 * width - 1)
    accepted (fromIntegral precision) (fromIntegral width) True H.=== expected
    compatible (fromIntegral precision) (fromIntegral width) H.=== expected
  check "generated widths/precisions agree with independent exact powers" passed
  -- Only metadata is parsed. Declared byte widths must never drive a value
  -- allocation or an enormous exponent before normal read limits can apply.
  let bytes = T.encodeCompact (metadata maxBound (Just maxBound) True 7)
  _ <- evaluate (BS.length bytes)
  let warm = T.encodeCompact (metadata 1 (Just maxBound) True 7)
  _ <- evaluate (V.length (case F.readFooterRaw warm of Right file -> P.fmSchema file; Left message -> error message))
  before <- getAllocationCounter
  value <- evaluate (case F.readFooterRaw bytes of Right file -> V.length (P.fmSchema file); Left message -> error message)
  after <- getAllocationCounter
  check "huge valid width uses bounded metadata memory" (value == 2 && before - after < 1024 * 1024)
  putStrLn "DECIMAL full-width capacity, legacy/modern, exact-boundary and metadata-memory regressions passed"