packages feed

duckdb-ffi-0.1.4.1: test/Utils.hs

{-# LANGUAGE BlockArguments #-}

module Utils (
    withDatabase,
    withConnection,
    withResult,
    withResultCString,
    withValue,
    withDuckValue,
    destroyDuckValue,
    withLogicalType,
    destroyLogicalType,
    withSelectionVector,
    withScalarFunction,
    withVector,
    withVectorOfType,
    setAllValid,
    clearValidityBit,
    plusWord,
    destroyErrorData,
) where

import Control.Exception (bracket)
import Control.Monad (forM_)
import Data.Bits (clearBit, setBit)
import Data.Word (Word64)
import Database.DuckDB.FFI
import Foreign.C.String (CString, withCString)
import Foreign.Marshal.Alloc (alloca)
import Foreign.Ptr (Ptr, plusPtr)
import Foreign.Storable (peek, poke, sizeOf)
import Test.Tasty.HUnit ((@?=))

withDatabase :: (DuckDBDatabase -> IO a) -> IO a
withDatabase action =
    withCString ":memory:" \path ->
        alloca \dbPtr -> do
            c_duckdb_open path dbPtr >>= (@?= DuckDBSuccess)
            db <- peek dbPtr
            result <- action db
            c_duckdb_close dbPtr
            pure result

withConnection :: DuckDBDatabase -> (DuckDBConnection -> IO a) -> IO a
withConnection db = bracket acquire release
  where
    acquire =
        alloca \connPtr -> do
            c_duckdb_connect db connPtr >>= (@?= DuckDBSuccess)
            peek connPtr
    release conn =
        alloca \connPtr -> do
            poke connPtr conn
            c_duckdb_disconnect connPtr

withResult :: DuckDBConnection -> String -> (Ptr DuckDBResult -> IO a) -> IO a
withResult conn sql action =
    withCString sql \sqlPtr -> withResultCString conn sqlPtr action

withResultCString :: DuckDBConnection -> CString -> (Ptr DuckDBResult -> IO a) -> IO a
withResultCString conn sql action =
    alloca \resPtr -> do
        c_duckdb_query conn sql resPtr >>= (@?= DuckDBSuccess)
        result <- action resPtr
        c_duckdb_destroy_result resPtr
        pure result

withValue :: IO DuckDBValue -> (DuckDBValue -> IO a) -> IO a
withValue acquire = bracket acquire destroyDuckValue

withDuckValue :: IO DuckDBValue -> (DuckDBValue -> IO a) -> IO a
withDuckValue = withValue

destroyDuckValue :: DuckDBValue -> IO ()
destroyDuckValue value =
    alloca \ptr -> poke ptr value >> c_duckdb_destroy_value ptr

withLogicalType :: IO DuckDBLogicalType -> (DuckDBLogicalType -> IO a) -> IO a
withLogicalType acquire = bracket acquire destroyLogicalType

destroyLogicalType :: DuckDBLogicalType -> IO ()
destroyLogicalType lt =
    alloca \ptr -> poke ptr lt >> c_duckdb_destroy_logical_type ptr

withSelectionVector :: DuckDBIdx -> (DuckDBSelectionVector -> IO a) -> IO a
withSelectionVector n = bracket (c_duckdb_create_selection_vector n) c_duckdb_destroy_selection_vector

withScalarFunction :: (DuckDBScalarFunction -> IO a) -> IO a
withScalarFunction = bracket c_duckdb_create_scalar_function destroy
  where
    destroy fun =
        alloca \ptr -> poke ptr fun >> c_duckdb_destroy_scalar_function ptr

withVector :: IO DuckDBVector -> (DuckDBVector -> IO a) -> IO a
withVector acquire = bracket acquire destroyVector
  where
    destroyVector vec =
        alloca \ptr -> poke ptr vec >> c_duckdb_destroy_vector ptr

withVectorOfType :: DuckDBLogicalType -> DuckDBIdx -> (DuckDBVector -> IO a) -> IO a
withVectorOfType lt capacity = withVector (c_duckdb_create_vector lt capacity)

setAllValid :: Ptr Word64 -> Int -> IO ()
setAllValid mask count =
    let totalWords = max 1 ((count + 63) `div` 64)
     in forM_ [0 .. totalWords - 1] \wordIdx -> do
            let start = wordIdx * 64
                end = min count (start + 64)
                bits = foldl setBit 0 [0 .. end - start - 1]
            poke (mask `plusWord` wordIdx) bits

clearValidityBit :: Ptr Word64 -> Int -> IO ()
clearValidityBit mask idx = do
    let wordIdx = idx `div` 64
        bitIdx = idx `mod` 64
        entryPtr = mask `plusWord` wordIdx
    current <- peek entryPtr
    poke entryPtr (clearBit current bitIdx)

plusWord :: Ptr Word64 -> Int -> Ptr Word64
plusWord base idx = base `plusPtr` (idx * sizeOf (undefined :: Word64))

destroyErrorData :: DuckDBErrorData -> IO ()
destroyErrorData errData =
    alloca \ptr -> poke ptr errData >> c_duckdb_destroy_error_data ptr