wireform-parquet-0.1.0.0: src/Parquet/Variant/Parquet.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedSums #-}
{-# LANGUAGE UnboxedTuples #-}
{- | Parquet Variant v1 columns. A null column slot is distinct
from the present Variant null value. Binary payloads are decoded only by
these explicit semantic operations; ordinary Parquet binary reads preserve
bytes, including payloads that are not valid Variants.
-}
module Parquet.Variant.Parquet (
VariantReader,
compileVariantReader,
compileVariantReaderWithLimits,
variantAt,
variantAt#,
variantToNestedRow,
variantToNestedRowWithLimits,
buildVariantParquetFile,
buildVariantParquetFileWithLimits,
readVariantColumn,
readVariantColumnWithLimits,
) where
import Arrow.Column qualified as C
import Arrow.Types qualified as A
import Control.Monad (unless)
import Data.ByteString (ByteString)
import Data.Text (Text)
import Data.Vector qualified as V
import GHC.Exts (Int (I#), Int#)
import Parquet.Arrow qualified as Arrow
import Parquet.Nested qualified as N
import Parquet.Read qualified as Read
import Parquet.Types qualified as P
import Parquet.Variant qualified as Variant
import Parquet.Variant.Arrow qualified as VariantArrow
import Parquet.Variant.Schema qualified as VariantSchema
data VariantReader = VariantReader
{ readerLength :: {-# UNPACK #-} !Int
, readerValidity :: !(Maybe C.Validity)
, readerMetadata :: !C.ColumnArray
, readerValue :: !C.ColumnArray
, readerLimits :: !Variant.VariantLimits
}
compileVariantReader :: C.ColumnArray -> Either String VariantReader
compileVariantReader = compileVariantReaderWithLimits Variant.defaultVariantLimits
{- | Compile named binary child views once. Accepts ordinary/large/view
binary arrays. Required children are checked only when the parent is
present, so null parents mask arbitrary child storage.
-}
compileVariantReaderWithLimits :: Variant.VariantLimits -> C.ColumnArray -> Either String VariantReader
compileVariantReaderWithLimits limits column = case column of
C.ColStruct count validity children -> do
unless
(count >= 0 && V.length children == 2 && V.length (V.filter ((== "metadata") . fst) children) == 1 && V.length (V.filter ((== "value") . fst) children) == 1)
(Left "Parquet.Variant: unshredded column requires unique metadata and value fields")
metadata <- named "metadata" children
value <- named "value" children
unless
(binary metadata && binary value && C.columnLength metadata >= count && C.columnLength value >= count)
(Left "Parquet.Variant: metadata/value must be binary arrays covering the group")
unless
(Variant.variantMaxDepth limits >= 0 && Variant.variantMaxValues limits > 0 && Variant.variantMaxDictionaryEntries limits >= 0)
(Left "Parquet.Variant: invalid resource limits")
pure (VariantReader count validity metadata value limits)
_ -> Left "Parquet.Variant: expected a struct column"
where
named name children = maybe (Left "Parquet.Variant: missing binary field") (Right . snd) (V.find ((== name) . fst) children)
binary value = case value of
C.ColBinary {} -> True
C.ColLargeBinary {} -> True
C.ColBinaryView {} -> True
_ -> False
variantAt :: VariantReader -> Int -> Either String (Maybe Variant.Variant)
variantAt reader (I# index) = case variantAt# reader index of
(# message | | #) -> Left message
(# | value | #) -> Right (Just value)
(# | | () #) -> Right Nothing
variantAt# :: VariantReader -> Int# -> (# String | Variant.Variant | () #)
variantAt# reader index#
| index < 0 || index >= readerLength reader = (# "Parquet.Variant: row index out of range" | | #)
| not (C.isValidAt (readerValidity reader) index) = (# | | () #)
| otherwise = case C.anyBytesAt (readerMetadata reader) index of
Nothing -> (# "Parquet.Variant: present group has null metadata" | | #)
Just metadata -> case C.anyBytesAt (readerValue reader) index of
Nothing -> (# "Parquet.Variant: present unshredded group has null value" | | #)
Just value -> case Variant.decodeVariantWithLimits (readerLimits reader) metadata value of
Left message -> (# message | | #)
Right decoded -> (# | decoded | #)
where
index = I# index#
{-# INLINE variantAt# #-}
variantToNestedRow :: Maybe Variant.Variant -> Either String N.NestedRow
variantToNestedRow = variantToNestedRowWithLimits Variant.defaultVariantLimits
variantToNestedRowWithLimits :: Variant.VariantLimits -> Maybe Variant.Variant -> Either String N.NestedRow
variantToNestedRowWithLimits _ Nothing = Right N.NRNull
variantToNestedRowWithLimits limits (Just value) = do
encoded <- Variant.encodeVariantWithLimits limits value
pure (N.NRVariantBytes (Variant.variantMetadata encoded) (Variant.variantValue encoded))
buildVariantParquetFile :: Text -> V.Vector (Maybe Variant.Variant) -> Either String ByteString
buildVariantParquetFile = buildVariantParquetFileWithLimits Variant.defaultVariantLimits
buildVariantParquetFileWithLimits :: Variant.VariantLimits -> Text -> V.Vector (Maybe Variant.Variant) -> Either String ByteString
buildVariantParquetFileWithLimits limits name values = do
rows <- V.mapM (variantToNestedRowWithLimits limits) values
N.buildNestedFile (V.singleton (name, N.NSOptional (N.NSVariantWithLimits limits))) (V.singleton rows)
readVariantColumn :: Read.ParquetFile -> Int -> Text -> Either String (V.Vector (Maybe Variant.Variant))
readVariantColumn = readVariantColumnWithLimits Variant.defaultVariantLimits
{- | Read one top-level VARIANT(1) column, reconstructing typed primitives,
objects and arrays with the original shared metadata dictionary.
-}
readVariantColumnWithLimits :: Variant.VariantLimits -> Read.ParquetFile -> Int -> Text -> Either String (V.Vector (Maybe Variant.Variant))
readVariantColumnWithLimits limits file group name = do
schema <- Arrow.parquetFileArrowSchemaChecked file
field <- maybe (Left "Parquet.Variant: column not found") Right (V.find ((== name) . A.fieldName) (A.arrowFields schema))
unless (VariantSchema.isVariantField field) (Left "Parquet.Variant: expected VARIANT(1) schema")
columns <- either (Left . show) Right (Arrow.parquetRowGroupToArrow schema {A.arrowFields = V.singleton field} file group)
let schemaLimits = VariantSchema.VariantSchemaLimits 128 (V.length (P.fmSchema (Read.pfFooter file)))
reader <- VariantArrow.compileVariantArrowReaderWithLimits limits schemaLimits field (V.head columns)
V.generateM (VariantArrow.variantArrowLength reader) (VariantArrow.variantArrowAt reader)