packages feed

keel-abi-0.1.0.0: src/Keel/Abi/Arrow/Raw.hs

-- | The Apache Arrow C Data Interface and C Stream Interface structs,
-- 1:1 and unmanaged.
--
-- Layouts are written out by hand against the frozen ABI
-- (<https://arrow.apache.org/docs/format/CDataInterface.html>), so this
-- module needs no headers and the library ships no C sources. The
-- test-suite layout gate (@test\/cbits\/layout_gate.c@) re-derives every
-- offset with a real C compiler and fails the build on any disagreement;
-- 'arrowSchemaLayout' & friends exist for that gate.
--
-- Everything here is raw: 'Ptr'-level records, manual ownership. The
-- release-callback discipline of the spec applies — consumers must call
-- 'releaseArrowSchema' \/ 'releaseArrowArray' exactly once when done, and
-- a moved (released) struct has a null 'schemaRelease' \/ 'arrayRelease'.
-- Managed import\/export lives one layer up, in @Keel.Abi.Arrow@.
module Keel.Abi.Arrow.Raw
  ( -- * ArrowSchema
    ArrowSchema (..)
  , emptyArrowSchema
  , releaseArrowSchema
  , arrowFlagDictionaryOrdered
  , arrowFlagNullable
  , arrowFlagMapKeysSorted

    -- * ArrowArray
  , ArrowArray (..)
  , emptyArrowArray
  , releaseArrowArray

    -- * ArrowArrayStream
  , ArrowArrayStream (..)
  , StreamGetSchemaFn
  , StreamGetNextFn
  , StreamGetLastErrorFn
  , callStreamGetSchema
  , callStreamGetNext
  , callStreamGetLastError
  , releaseArrowArrayStream

    -- * Layout tables (consumed by the test-suite layout gate)
  , arrowSchemaLayout
  , arrowArrayLayout
  , arrowArrayStreamLayout
  ) where

import Control.Monad (unless)
import Data.Int (Int64)
import Foreign.C.String (CString)
import Foreign.C.Types (CInt (..))
import Foreign.Ptr (FunPtr, Ptr, nullFunPtr, nullPtr)
import Foreign.Storable (Storable (..))

-- ---------------------------------------------------------------------
-- ArrowSchema

-- | @struct ArrowSchema@. Field semantics (format strings, ownership)
-- are exactly those of the C Data Interface spec.
data ArrowSchema = ArrowSchema
  { schemaFormat :: CString
  , schemaName :: CString
  , schemaMetadata :: CString
  , schemaFlags :: Int64
  , schemaNChildren :: Int64
  , schemaChildren :: Ptr (Ptr ArrowSchema)
  , schemaDictionary :: Ptr ArrowSchema
  , schemaRelease :: FunPtr (Ptr ArrowSchema -> IO ())
  , schemaPrivateData :: Ptr ()
  }

-- | The @ARROW_FLAG_*@ bits of 'schemaFlags'.
arrowFlagDictionaryOrdered, arrowFlagNullable, arrowFlagMapKeysSorted :: Int64
arrowFlagDictionaryOrdered = 1
arrowFlagNullable = 2
arrowFlagMapKeysSorted = 4

oSchemaFormat, oSchemaName, oSchemaMetadata, oSchemaFlags, oSchemaNChildren,
  oSchemaChildren, oSchemaDictionary, oSchemaRelease, oSchemaPrivateData,
  szArrowSchema :: Int
oSchemaFormat = 0
oSchemaName = 8
oSchemaMetadata = 16
oSchemaFlags = 24
oSchemaNChildren = 32
oSchemaChildren = 40
oSchemaDictionary = 48
oSchemaRelease = 56
oSchemaPrivateData = 64
szArrowSchema = 72

-- | @(sizeof, [(field, offset)])@ in declaration order — compared
-- verbatim against C @offsetof@ by the layout gate.
arrowSchemaLayout :: (Int, [(String, Int)])
arrowSchemaLayout =
  ( szArrowSchema
  , [ ("format", oSchemaFormat)
    , ("name", oSchemaName)
    , ("metadata", oSchemaMetadata)
    , ("flags", oSchemaFlags)
    , ("n_children", oSchemaNChildren)
    , ("children", oSchemaChildren)
    , ("dictionary", oSchemaDictionary)
    , ("release", oSchemaRelease)
    , ("private_data", oSchemaPrivateData)
    ]
  )

instance Storable ArrowSchema where
  sizeOf _ = szArrowSchema
  alignment _ = 8
  peek p =
    ArrowSchema
      <$> peekByteOff p oSchemaFormat
      <*> peekByteOff p oSchemaName
      <*> peekByteOff p oSchemaMetadata
      <*> peekByteOff p oSchemaFlags
      <*> peekByteOff p oSchemaNChildren
      <*> peekByteOff p oSchemaChildren
      <*> peekByteOff p oSchemaDictionary
      <*> peekByteOff p oSchemaRelease
      <*> peekByteOff p oSchemaPrivateData
  poke p s = do
    pokeByteOff p oSchemaFormat (schemaFormat s)
    pokeByteOff p oSchemaName (schemaName s)
    pokeByteOff p oSchemaMetadata (schemaMetadata s)
    pokeByteOff p oSchemaFlags (schemaFlags s)
    pokeByteOff p oSchemaNChildren (schemaNChildren s)
    pokeByteOff p oSchemaChildren (schemaChildren s)
    pokeByteOff p oSchemaDictionary (schemaDictionary s)
    pokeByteOff p oSchemaRelease (schemaRelease s)
    pokeByteOff p oSchemaPrivateData (schemaPrivateData s)

-- | All-null\/zero template — the starting point for building an export,
-- and the spec's representation of a released\/moved struct.
emptyArrowSchema :: ArrowSchema
emptyArrowSchema =
  ArrowSchema
    { schemaFormat = nullPtr
    , schemaName = nullPtr
    , schemaMetadata = nullPtr
    , schemaFlags = 0
    , schemaNChildren = 0
    , schemaChildren = nullPtr
    , schemaDictionary = nullPtr
    , schemaRelease = nullFunPtr
    , schemaPrivateData = nullPtr
    }

foreign import ccall "dynamic"
  callSchemaRelease :: FunPtr (Ptr ArrowSchema -> IO ()) -> Ptr ArrowSchema -> IO ()

-- | Call the struct's release callback unless it is already null (i.e.
-- the struct was moved or released before). Idempotent per the spec:
-- the callback itself nulls the release member.
releaseArrowSchema :: Ptr ArrowSchema -> IO ()
releaseArrowSchema p = do
  fp <- peekByteOff p oSchemaRelease
  unless (fp == nullFunPtr) (callSchemaRelease fp p)

-- ---------------------------------------------------------------------
-- ArrowArray

-- | @struct ArrowArray@. Buffer count and meaning follow the format
-- string of the corresponding 'ArrowSchema'.
data ArrowArray = ArrowArray
  { arrayLength :: Int64
  , arrayNullCount :: Int64
  , arrayOffset :: Int64
  , arrayNBuffers :: Int64
  , arrayNChildren :: Int64
  , arrayBuffers :: Ptr (Ptr ())
  , arrayChildren :: Ptr (Ptr ArrowArray)
  , arrayDictionary :: Ptr ArrowArray
  , arrayRelease :: FunPtr (Ptr ArrowArray -> IO ())
  , arrayPrivateData :: Ptr ()
  }

oArrayLength, oArrayNullCount, oArrayOffset, oArrayNBuffers, oArrayNChildren,
  oArrayBuffers, oArrayChildren, oArrayDictionary, oArrayRelease,
  oArrayPrivateData, szArrowArray :: Int
oArrayLength = 0
oArrayNullCount = 8
oArrayOffset = 16
oArrayNBuffers = 24
oArrayNChildren = 32
oArrayBuffers = 40
oArrayChildren = 48
oArrayDictionary = 56
oArrayRelease = 64
oArrayPrivateData = 72
szArrowArray = 80

-- | @(sizeof, [(field, offset)])@ in declaration order.
arrowArrayLayout :: (Int, [(String, Int)])
arrowArrayLayout =
  ( szArrowArray
  , [ ("length", oArrayLength)
    , ("null_count", oArrayNullCount)
    , ("offset", oArrayOffset)
    , ("n_buffers", oArrayNBuffers)
    , ("n_children", oArrayNChildren)
    , ("buffers", oArrayBuffers)
    , ("children", oArrayChildren)
    , ("dictionary", oArrayDictionary)
    , ("release", oArrayRelease)
    , ("private_data", oArrayPrivateData)
    ]
  )

instance Storable ArrowArray where
  sizeOf _ = szArrowArray
  alignment _ = 8
  peek p =
    ArrowArray
      <$> peekByteOff p oArrayLength
      <*> peekByteOff p oArrayNullCount
      <*> peekByteOff p oArrayOffset
      <*> peekByteOff p oArrayNBuffers
      <*> peekByteOff p oArrayNChildren
      <*> peekByteOff p oArrayBuffers
      <*> peekByteOff p oArrayChildren
      <*> peekByteOff p oArrayDictionary
      <*> peekByteOff p oArrayRelease
      <*> peekByteOff p oArrayPrivateData
  poke p a = do
    pokeByteOff p oArrayLength (arrayLength a)
    pokeByteOff p oArrayNullCount (arrayNullCount a)
    pokeByteOff p oArrayOffset (arrayOffset a)
    pokeByteOff p oArrayNBuffers (arrayNBuffers a)
    pokeByteOff p oArrayNChildren (arrayNChildren a)
    pokeByteOff p oArrayBuffers (arrayBuffers a)
    pokeByteOff p oArrayChildren (arrayChildren a)
    pokeByteOff p oArrayDictionary (arrayDictionary a)
    pokeByteOff p oArrayRelease (arrayRelease a)
    pokeByteOff p oArrayPrivateData (arrayPrivateData a)

-- | All-null\/zero template — the starting point for building an export,
-- and the spec's representation of a released\/moved struct (an
-- end-of-stream @get_next@ writes exactly this).
emptyArrowArray :: ArrowArray
emptyArrowArray =
  ArrowArray
    { arrayLength = 0
    , arrayNullCount = 0
    , arrayOffset = 0
    , arrayNBuffers = 0
    , arrayNChildren = 0
    , arrayBuffers = nullPtr
    , arrayChildren = nullPtr
    , arrayDictionary = nullPtr
    , arrayRelease = nullFunPtr
    , arrayPrivateData = nullPtr
    }

foreign import ccall "dynamic"
  callArrayRelease :: FunPtr (Ptr ArrowArray -> IO ()) -> Ptr ArrowArray -> IO ()

-- | Call the struct's release callback unless it is already null.
releaseArrowArray :: Ptr ArrowArray -> IO ()
releaseArrowArray p = do
  fp <- peekByteOff p oArrayRelease
  unless (fp == nullFunPtr) (callArrayRelease fp p)

-- ---------------------------------------------------------------------
-- ArrowArrayStream

-- | @get_schema@: fills the out-struct, returns 0 or an errno-style code.
type StreamGetSchemaFn = Ptr ArrowArrayStream -> Ptr ArrowSchema -> IO CInt

-- | @get_next@: fills the out-struct, or marks end-of-stream by leaving
-- its release member null. Returns 0 or an errno-style code.
type StreamGetNextFn = Ptr ArrowArrayStream -> Ptr ArrowArray -> IO CInt

-- | @get_last_error@: description of the last error, or null.
type StreamGetLastErrorFn = Ptr ArrowArrayStream -> IO CString

-- | @struct ArrowArrayStream@ — all members are function pointers plus
-- @private_data@; drive it with the @call*@ wrappers below.
data ArrowArrayStream = ArrowArrayStream
  { streamGetSchema :: FunPtr StreamGetSchemaFn
  , streamGetNext :: FunPtr StreamGetNextFn
  , streamGetLastError :: FunPtr StreamGetLastErrorFn
  , streamRelease :: FunPtr (Ptr ArrowArrayStream -> IO ())
  , streamPrivateData :: Ptr ()
  }

oStreamGetSchema, oStreamGetNext, oStreamGetLastError, oStreamRelease,
  oStreamPrivateData, szArrowArrayStream :: Int
oStreamGetSchema = 0
oStreamGetNext = 8
oStreamGetLastError = 16
oStreamRelease = 24
oStreamPrivateData = 32
szArrowArrayStream = 40

-- | @(sizeof, [(field, offset)])@ in declaration order.
arrowArrayStreamLayout :: (Int, [(String, Int)])
arrowArrayStreamLayout =
  ( szArrowArrayStream
  , [ ("get_schema", oStreamGetSchema)
    , ("get_next", oStreamGetNext)
    , ("get_last_error", oStreamGetLastError)
    , ("release", oStreamRelease)
    , ("private_data", oStreamPrivateData)
    ]
  )

instance Storable ArrowArrayStream where
  sizeOf _ = szArrowArrayStream
  alignment _ = 8
  peek p =
    ArrowArrayStream
      <$> peekByteOff p oStreamGetSchema
      <*> peekByteOff p oStreamGetNext
      <*> peekByteOff p oStreamGetLastError
      <*> peekByteOff p oStreamRelease
      <*> peekByteOff p oStreamPrivateData
  poke p s = do
    pokeByteOff p oStreamGetSchema (streamGetSchema s)
    pokeByteOff p oStreamGetNext (streamGetNext s)
    pokeByteOff p oStreamGetLastError (streamGetLastError s)
    pokeByteOff p oStreamRelease (streamRelease s)
    pokeByteOff p oStreamPrivateData (streamPrivateData s)

-- | Invoke a stream's @get_schema@ member (pass the struct's own
-- pointer as the first argument).
foreign import ccall "dynamic"
  callStreamGetSchema :: FunPtr StreamGetSchemaFn -> StreamGetSchemaFn

-- | Invoke a stream's @get_next@ member.
foreign import ccall "dynamic"
  callStreamGetNext :: FunPtr StreamGetNextFn -> StreamGetNextFn

-- | Invoke a stream's @get_last_error@ member.
foreign import ccall "dynamic"
  callStreamGetLastError :: FunPtr StreamGetLastErrorFn -> StreamGetLastErrorFn

foreign import ccall "dynamic"
  callStreamRelease :: FunPtr (Ptr ArrowArrayStream -> IO ()) -> Ptr ArrowArrayStream -> IO ()

-- | Call the stream's release callback unless it is already null.
releaseArrowArrayStream :: Ptr ArrowArrayStream -> IO ()
releaseArrowArrayStream p = do
  fp <- peekByteOff p oStreamRelease
  unless (fp == nullFunPtr) (callStreamRelease fp p)