packages feed

wireform-parquet-0.1.0.0: src/Parquet/Variant/Nested.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedSums #-}
{-# LANGUAGE UnboxedTuples #-}

{- | Internal semantic validation after every native leaf's structural and
byte-count preflight. Typed primitives were already range-checked there;
this pass checks shared dictionaries, conflicts and one global tree budget.
-}
module Parquet.Variant.Nested (normalizeStorageRow, validateStorageRow) where

import Data.ByteString qualified as BS
import Data.Map.Strict qualified as M
import Data.Set qualified as Set
import Data.Vector qualified as V
import GHC.Exts (Int (I#), Int#, isTrue#, (+#), (==#))
import Parquet.Nested.Types
import Parquet.Variant qualified as B


-- Native array elements must actually contain a Variant value. The Arrow
-- convention permits both slots to be missing and reads that as Variant null;
-- supply binary 00 in the native fallback without losing any array elements.
-- The same normalization applies to a missing required root value. Metadata
-- and all supplied fragments are retained exactly, never re-encoded.
normalizeStorageRow :: VariantStorageSchema -> NestedRow -> NestedRow
normalizeStorageRow schema = node True (storageNode schema)
  where
    node required plan row = case row of
      NRStruct fields
        | V.length fields == storageFieldCount plan ->
            let fallback = slot (storageValueIndex plan) fields
                typed = slot (storageTypedIndex plan) fields
                value = if required && missing fallback && missing typed then NRLeaf (LvBinary (BS.singleton 0)) else fallback
                typedValue = maybe typed (`typedRow` typed) (storageTyped plan)
                output = V.imap (\index original -> if index == storageValueIndex plan then value else if index == storageTypedIndex plan then typedValue else original) fields
            in if not (needs required plan row) then row else NRStruct (if storageValueIndex plan < 0 then V.snoc output value else output)
      _ -> row
    needs required plan row = case row of
      NRStruct fields
        | V.length fields == storageFieldCount plan ->
            storageValueIndex plan < 0
              || (required && missing (slot (storageValueIndex plan) fields) && missing (slot (storageTypedIndex plan) fields))
              || (storageNormalizationBelow plan && maybe False (\kind -> typedNeeds kind (slot (storageTypedIndex plan) fields)) (storageTyped plan))
      _ -> False
    typedNeeds kind row = case (kind, row) of
      (StorageObject children _, NRStruct fields) | V.length fields == V.length children -> V.any (\child -> needs False (storageChildNode child) (V.unsafeIndex fields (storageChildIndex child))) children
      (StorageArray child, NRList values) -> V.any (needs True child) values
      _ -> False
    typedRow kind row = case (kind, row) of
      (StorageObject children _, NRStruct fields)
        | V.length fields == V.length children ->
            NRStruct (V.zipWith (\child value -> node False (storageChildNode child) value) children fields)
      (StorageArray child, NRList values) -> NRList (V.map (node True child) values)
      _ -> row


slot :: Int -> V.Vector NestedRow -> NestedRow
slot index fields = maybe NRNull id (fields V.!? index)
{-# INLINE slot #-}


missing :: NestedRow -> Bool
missing NRNull = True
missing _ = False
{-# INLINE missing #-}


type Checked# = (# String | Int# #)


type NodeChecked# = (# String | Int# | () #)


validateStorageRow :: VariantStorageSchema -> NestedRow -> Either String ()
validateStorageRow _ NRNull = Right ()
validateStorageRow schema row = case row of
  NRStruct fields | V.length fields == storageFieldCount (storageNode schema) -> case slot (storageMetadataIndex schema) fields of
    NRLeaf (LvBinary metadata) -> case B.compileVariantMetadataWithLimits limits metadata of
      Left message -> Left message
      Right dictionary -> case B.variantMaxValues limits of
        I# budget -> case required# limits dictionary (storageNode schema) 0# budget row of
          (# message | #) -> Left message
          (# | _ #) -> Right ()
    _ -> Left "Parquet.Variant.Nested: present root requires binary metadata"
  _ -> Left "Parquet.Variant.Nested: root storage shape mismatch"
  where
    limits = storageLimits schema


charge# :: B.VariantLimits -> Int# -> Int# -> Checked#
charge# limits depth budget
  | I# depth > B.variantMaxDepth limits = (# "Parquet.Variant.Nested: nesting depth limit exceeded" | #)
  | I# budget <= 0 = (# "Parquet.Variant.Nested: value count limit exceeded" | #)
  | otherwise = (# | budget +# (-1#) #)
{-# INLINE charge# #-}


required# :: B.VariantLimits -> B.VariantMetadata -> VariantStorageNode -> Int# -> Int# -> NestedRow -> Checked#
required# limits dictionary plan depth budget row = case checkNode# limits dictionary plan depth budget row of
  (# message | | #) -> (# message | #)
  (# | left | #) -> (# | left #)
  (# | | () #) -> charge# limits depth budget
{-# INLINE required# #-}


presence :: VariantStorageNode -> NestedRow -> Either String Bool
presence plan (NRStruct fields)
  | V.length fields /= storageFieldCount plan = Left "Parquet.Variant.Nested: wrapper field count mismatch"
  | storageValueRequired plan && missing value = Left "Parquet.Variant.Nested: null at required value field"
  | storageTypedRequired plan && missing typed = Left "Parquet.Variant.Nested: null at required typed field"
  | otherwise = Right (not (missing value && missing typed))
  where
    value = slot (storageValueIndex plan) fields
    typed = slot (storageTypedIndex plan) fields
presence _ _ = Left "Parquet.Variant.Nested: shredded wrapper must be a required struct"
{-# INLINE presence #-}


checkNode# :: B.VariantLimits -> B.VariantMetadata -> VariantStorageNode -> Int# -> Int# -> NestedRow -> NodeChecked#
checkNode# limits dictionary plan depth budget row = case presence plan row of
  Left message -> (# message | | #)
  Right False -> (# | | () #)
  Right True
    | NRStruct fields <- row ->
        let fallback = slot (storageValueIndex plan) fields
            typed = slot (storageTypedIndex plan) fields
        in case (storageTyped plan, typed) of
             (Just (StorageObject children names), NRStruct values) ->
               case fallback of
                 NRNull -> case charge# limits depth budget of
                   (# message | #) -> (# message | | #)
                   (# | left #) -> object children values left
                 NRLeaf (LvBinary bytes) -> case B.decodeVariantFragment# limits dictionary depth budget bytes of
                   (# message | #) -> (# message | | #)
                   (# | (# B.VObject rest, left #) #)
                     | M.foldrWithKey (\name _ found -> Set.member name names || found) False rest -> (# "Parquet.Variant.Nested: object field appears in typed and fallback storage" | | #)
                     | otherwise -> object children values left
                   _ -> (# "Parquet.Variant.Nested: partial fallback must be an object" | | #)
                 _ -> badShapeResult# ()
             (Just (StorageArray child), NRList values) -> case fallback of
               NRNull -> case charge# limits depth budget of
                 (# message | #) -> (# message | | #)
                 (# | left #) -> case array# limits dictionary child (depth +# 1#) left values 0# of
                   (# message | #) -> (# message | | #)
                   (# | remaining #) -> (# | remaining | #)
               _ -> conflictResult# ()
             (Just StoragePrimitive, NRLeaf _) -> case fallback of
               NRNull -> case charge# limits depth budget of
                 (# message | #) -> (# message | | #)
                 (# | left #) -> (# | left | #)
               _ -> conflictResult# ()
             (_, NRNull) -> case fallback of
               NRLeaf (LvBinary bytes) -> case B.decodeVariantFragment# limits dictionary depth budget bytes of
                 (# message | #) -> (# message | | #)
                 (# | (# value, remaining #) #) -> case (storageTyped plan, value) of
                   (Just StorageObject {}, B.VObject {}) -> (# "Parquet.Variant.Nested: object is missing its typed storage" | | #)
                   (Just StorageArray {}, B.VArray {}) -> (# "Parquet.Variant.Nested: array is missing its typed storage" | | #)
                   _ -> (# | remaining | #)
               _ -> badShapeResult# ()
             _ -> badShapeResult# ()
    where
      object children values left
        | V.length values /= V.length children = badShapeResult# ()
        | otherwise = case object# limits dictionary children (depth +# 1#) left values 0# of
            (# message | #) -> (# message | | #)
            (# | remaining #) -> (# | remaining | #)
  _ -> badShapeResult# ()


badShapeResult# :: () -> NodeChecked#
badShapeResult# () = (# "Parquet.Variant.Nested: typed storage shape mismatch" | | #)


conflictResult# :: () -> NodeChecked#
conflictResult# () = (# "Parquet.Variant.Nested: value and typed_value conflict" | | #)


object# :: B.VariantLimits -> B.VariantMetadata -> V.Vector VariantStorageChild -> Int# -> Int# -> V.Vector NestedRow -> Int# -> Checked#
object# limits dictionary children depth budget values index
  | isTrue# (index ==# count) = (# | budget #)
  | otherwise =
      let child = V.unsafeIndex children (I# index)
          row = V.unsafeIndex values (storageChildIndex child)
      in case presence (storageChildNode child) row of
           Left message -> (# message | #)
           Right False -> object# limits dictionary children depth budget values (index +# 1#)
           Right True | not (B.metadataContains (storageChildName child) dictionary) -> (# "Parquet.Variant.Nested: typed field name is missing from the shared dictionary" | #)
           _ -> case checkNode# limits dictionary (storageChildNode child) depth budget row of
             (# message | | #) -> (# message | #)
             (# | left | #) -> object# limits dictionary children depth left values (index +# 1#)
             (# | | () #) -> object# limits dictionary children depth budget values (index +# 1#)
  where
    I# count = V.length children


array# :: B.VariantLimits -> B.VariantMetadata -> VariantStorageNode -> Int# -> Int# -> V.Vector NestedRow -> Int# -> Checked#
array# limits dictionary child depth budget values index
  | isTrue# (index ==# count) = (# | budget #)
  | otherwise = case required# limits dictionary child depth budget (V.unsafeIndex values (I# index)) of
      (# message | #) -> (# message | #)
      (# | left #) -> array# limits dictionary child depth left values (index +# 1#)
  where
    I# count = V.length values