packages feed

duckdb-ffi 1.5.0.0 → 1.5.3.0

raw patch · 40 files changed

+967/−267 lines, 40 filesbuild-type:Customsetup-changedPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Database.DuckDB.FFI: ArrowArrayStream :: FunPtr (Ptr ArrowArrayStream -> Ptr ArrowSchema -> IO CInt) -> FunPtr (Ptr ArrowArrayStream -> Ptr ArrowArray -> IO CInt) -> FunPtr (Ptr ArrowArrayStream -> IO CString) -> FunPtr (Ptr ArrowArrayStream -> IO ()) -> Ptr () -> ArrowArrayStream
+ Database.DuckDB.FFI: DuckDBListEntry :: Word64 -> Word64 -> DuckDBListEntry
+ Database.DuckDB.FFI: [arrowStreamGetLastError] :: ArrowArrayStream -> FunPtr (Ptr ArrowArrayStream -> IO CString)
+ Database.DuckDB.FFI: [arrowStreamGetNext] :: ArrowArrayStream -> FunPtr (Ptr ArrowArrayStream -> Ptr ArrowArray -> IO CInt)
+ Database.DuckDB.FFI: [arrowStreamGetSchema] :: ArrowArrayStream -> FunPtr (Ptr ArrowArrayStream -> Ptr ArrowSchema -> IO CInt)
+ Database.DuckDB.FFI: [arrowStreamPrivateData] :: ArrowArrayStream -> Ptr ()
+ Database.DuckDB.FFI: [arrowStreamRelease] :: ArrowArrayStream -> FunPtr (Ptr ArrowArrayStream -> IO ())
+ Database.DuckDB.FFI: [duckDBListEntryLength] :: DuckDBListEntry -> Word64
+ Database.DuckDB.FFI: [duckDBListEntryOffset] :: DuckDBListEntry -> Word64
+ Database.DuckDB.FFI: data ArrowArrayStream
+ Database.DuckDB.FFI: data DuckDBListEntry
+ Database.DuckDB.FFI: mkArrowArrayRelease :: FunPtr (Ptr ArrowArray -> IO ()) -> Ptr ArrowArray -> IO ()
+ Database.DuckDB.FFI: mkArrowSchemaRelease :: FunPtr (Ptr ArrowSchema -> IO ()) -> Ptr ArrowSchema -> IO ()
+ Database.DuckDB.FFI: mkArrowStreamGetLastError :: FunPtr (Ptr ArrowArrayStream -> IO CString) -> Ptr ArrowArrayStream -> IO CString
+ Database.DuckDB.FFI: mkArrowStreamGetNext :: FunPtr (Ptr ArrowArrayStream -> Ptr ArrowArray -> IO CInt) -> Ptr ArrowArrayStream -> Ptr ArrowArray -> IO CInt
+ Database.DuckDB.FFI: mkArrowStreamGetSchema :: FunPtr (Ptr ArrowArrayStream -> Ptr ArrowSchema -> IO CInt) -> Ptr ArrowArrayStream -> Ptr ArrowSchema -> IO CInt
+ Database.DuckDB.FFI: mkArrowStreamRelease :: FunPtr (Ptr ArrowArrayStream -> IO ()) -> Ptr ArrowArrayStream -> IO ()
+ Database.DuckDB.FFI: releaseArrowArray :: Ptr ArrowArray -> IO ()
+ Database.DuckDB.FFI: releaseArrowSchema :: Ptr ArrowSchema -> IO ()
+ Database.DuckDB.FFI: releaseArrowStream :: Ptr ArrowArrayStream -> IO ()
+ Database.DuckDB.FFI: wrapArrowArrayRelease :: (Ptr ArrowArray -> IO ()) -> IO (FunPtr (Ptr ArrowArray -> IO ()))
+ Database.DuckDB.FFI: wrapArrowSchemaRelease :: (Ptr ArrowSchema -> IO ()) -> IO (FunPtr (Ptr ArrowSchema -> IO ()))
+ Database.DuckDB.FFI: wrapArrowStreamGetLastError :: (Ptr ArrowArrayStream -> IO CString) -> IO (FunPtr (Ptr ArrowArrayStream -> IO CString))
+ Database.DuckDB.FFI: wrapArrowStreamGetNext :: (Ptr ArrowArrayStream -> Ptr ArrowArray -> IO CInt) -> IO (FunPtr (Ptr ArrowArrayStream -> Ptr ArrowArray -> IO CInt))
+ Database.DuckDB.FFI: wrapArrowStreamGetSchema :: (Ptr ArrowArrayStream -> Ptr ArrowSchema -> IO CInt) -> IO (FunPtr (Ptr ArrowArrayStream -> Ptr ArrowSchema -> IO CInt))
+ Database.DuckDB.FFI: wrapArrowStreamRelease :: (Ptr ArrowArrayStream -> IO ()) -> IO (FunPtr (Ptr ArrowArrayStream -> IO ()))
- Database.DuckDB.FFI: DuckDBBignum :: !Ptr Word8 -> !DuckDBIdx -> !CBool -> DuckDBBignum
+ Database.DuckDB.FFI: DuckDBBignum :: Ptr Word8 -> DuckDBIdx -> CBool -> DuckDBBignum
- Database.DuckDB.FFI: DuckDBBit :: !Ptr Word8 -> !DuckDBIdx -> DuckDBBit
+ Database.DuckDB.FFI: DuckDBBit :: Ptr Word8 -> DuckDBIdx -> DuckDBBit
- Database.DuckDB.FFI: DuckDBBlob :: !Ptr () -> !DuckDBIdx -> DuckDBBlob
+ Database.DuckDB.FFI: DuckDBBlob :: Ptr () -> DuckDBIdx -> DuckDBBlob
- Database.DuckDB.FFI: DuckDBDateStruct :: !Int32 -> !Int8 -> !Int8 -> DuckDBDateStruct
+ Database.DuckDB.FFI: DuckDBDateStruct :: Int32 -> Int8 -> Int8 -> DuckDBDateStruct
- Database.DuckDB.FFI: DuckDBDecimal :: !Word8 -> !Word8 -> !DuckDBHugeInt -> DuckDBDecimal
+ Database.DuckDB.FFI: DuckDBDecimal :: Word8 -> Word8 -> DuckDBHugeInt -> DuckDBDecimal
- Database.DuckDB.FFI: DuckDBHugeInt :: !Word64 -> !Int64 -> DuckDBHugeInt
+ Database.DuckDB.FFI: DuckDBHugeInt :: Word64 -> Int64 -> DuckDBHugeInt
- Database.DuckDB.FFI: DuckDBInterval :: !Int32 -> !Int32 -> !Int64 -> DuckDBInterval
+ Database.DuckDB.FFI: DuckDBInterval :: Int32 -> Int32 -> Int64 -> DuckDBInterval
- Database.DuckDB.FFI: DuckDBQueryProgress :: !Double -> !Word64 -> !Word64 -> DuckDBQueryProgress
+ Database.DuckDB.FFI: DuckDBQueryProgress :: Double -> Word64 -> Word64 -> DuckDBQueryProgress
- Database.DuckDB.FFI: DuckDBResult :: !DuckDBIdx -> !DuckDBIdx -> !DuckDBIdx -> !Ptr DuckDBColumn -> !CString -> !Ptr () -> DuckDBResult
+ Database.DuckDB.FFI: DuckDBResult :: DuckDBIdx -> DuckDBIdx -> DuckDBIdx -> Ptr DuckDBColumn -> CString -> Ptr () -> DuckDBResult
- Database.DuckDB.FFI: DuckDBString :: !Ptr CChar -> !DuckDBIdx -> DuckDBString
+ Database.DuckDB.FFI: DuckDBString :: Ptr CChar -> DuckDBIdx -> DuckDBString
- Database.DuckDB.FFI: DuckDBTimeStruct :: !Int8 -> !Int8 -> !Int8 -> !Int32 -> DuckDBTimeStruct
+ Database.DuckDB.FFI: DuckDBTimeStruct :: Int8 -> Int8 -> Int8 -> Int32 -> DuckDBTimeStruct
- Database.DuckDB.FFI: DuckDBTimeTzStruct :: !DuckDBTimeStruct -> !Int32 -> DuckDBTimeTzStruct
+ Database.DuckDB.FFI: DuckDBTimeTzStruct :: DuckDBTimeStruct -> Int32 -> DuckDBTimeTzStruct
- Database.DuckDB.FFI: DuckDBTimestampStruct :: !DuckDBDateStruct -> !DuckDBTimeStruct -> DuckDBTimestampStruct
+ Database.DuckDB.FFI: DuckDBTimestampStruct :: DuckDBDateStruct -> DuckDBTimeStruct -> DuckDBTimestampStruct
- Database.DuckDB.FFI: DuckDBUHugeInt :: !Word64 -> !Word64 -> DuckDBUHugeInt
+ Database.DuckDB.FFI: DuckDBUHugeInt :: Word64 -> Word64 -> DuckDBUHugeInt
- Database.DuckDB.FFI: [duckDBBignumData] :: DuckDBBignum -> !Ptr Word8
+ Database.DuckDB.FFI: [duckDBBignumData] :: DuckDBBignum -> Ptr Word8
- Database.DuckDB.FFI: [duckDBBignumIsNegative] :: DuckDBBignum -> !CBool
+ Database.DuckDB.FFI: [duckDBBignumIsNegative] :: DuckDBBignum -> CBool
- Database.DuckDB.FFI: [duckDBBignumSize] :: DuckDBBignum -> !DuckDBIdx
+ Database.DuckDB.FFI: [duckDBBignumSize] :: DuckDBBignum -> DuckDBIdx
- Database.DuckDB.FFI: [duckDBBitData] :: DuckDBBit -> !Ptr Word8
+ Database.DuckDB.FFI: [duckDBBitData] :: DuckDBBit -> Ptr Word8
- Database.DuckDB.FFI: [duckDBBitSize] :: DuckDBBit -> !DuckDBIdx
+ Database.DuckDB.FFI: [duckDBBitSize] :: DuckDBBit -> DuckDBIdx
- Database.DuckDB.FFI: [duckDBBlobData] :: DuckDBBlob -> !Ptr ()
+ Database.DuckDB.FFI: [duckDBBlobData] :: DuckDBBlob -> Ptr ()
- Database.DuckDB.FFI: [duckDBBlobSize] :: DuckDBBlob -> !DuckDBIdx
+ Database.DuckDB.FFI: [duckDBBlobSize] :: DuckDBBlob -> DuckDBIdx
- Database.DuckDB.FFI: [duckDBDateStructDay] :: DuckDBDateStruct -> !Int8
+ Database.DuckDB.FFI: [duckDBDateStructDay] :: DuckDBDateStruct -> Int8
- Database.DuckDB.FFI: [duckDBDateStructMonth] :: DuckDBDateStruct -> !Int8
+ Database.DuckDB.FFI: [duckDBDateStructMonth] :: DuckDBDateStruct -> Int8
- Database.DuckDB.FFI: [duckDBDateStructYear] :: DuckDBDateStruct -> !Int32
+ Database.DuckDB.FFI: [duckDBDateStructYear] :: DuckDBDateStruct -> Int32
- Database.DuckDB.FFI: [duckDBDecimalScale] :: DuckDBDecimal -> !Word8
+ Database.DuckDB.FFI: [duckDBDecimalScale] :: DuckDBDecimal -> Word8
- Database.DuckDB.FFI: [duckDBDecimalValue] :: DuckDBDecimal -> !DuckDBHugeInt
+ Database.DuckDB.FFI: [duckDBDecimalValue] :: DuckDBDecimal -> DuckDBHugeInt
- Database.DuckDB.FFI: [duckDBDecimalWidth] :: DuckDBDecimal -> !Word8
+ Database.DuckDB.FFI: [duckDBDecimalWidth] :: DuckDBDecimal -> Word8
- Database.DuckDB.FFI: [duckDBHugeIntLower] :: DuckDBHugeInt -> !Word64
+ Database.DuckDB.FFI: [duckDBHugeIntLower] :: DuckDBHugeInt -> Word64
- Database.DuckDB.FFI: [duckDBHugeIntUpper] :: DuckDBHugeInt -> !Int64
+ Database.DuckDB.FFI: [duckDBHugeIntUpper] :: DuckDBHugeInt -> Int64
- Database.DuckDB.FFI: [duckDBIntervalDays] :: DuckDBInterval -> !Int32
+ Database.DuckDB.FFI: [duckDBIntervalDays] :: DuckDBInterval -> Int32
- Database.DuckDB.FFI: [duckDBIntervalMicros] :: DuckDBInterval -> !Int64
+ Database.DuckDB.FFI: [duckDBIntervalMicros] :: DuckDBInterval -> Int64
- Database.DuckDB.FFI: [duckDBIntervalMonths] :: DuckDBInterval -> !Int32
+ Database.DuckDB.FFI: [duckDBIntervalMonths] :: DuckDBInterval -> Int32
- Database.DuckDB.FFI: [duckDBQueryProgressPercentage] :: DuckDBQueryProgress -> !Double
+ Database.DuckDB.FFI: [duckDBQueryProgressPercentage] :: DuckDBQueryProgress -> Double
- Database.DuckDB.FFI: [duckDBQueryProgressRowsProcessed] :: DuckDBQueryProgress -> !Word64
+ Database.DuckDB.FFI: [duckDBQueryProgressRowsProcessed] :: DuckDBQueryProgress -> Word64
- Database.DuckDB.FFI: [duckDBQueryProgressTotalRows] :: DuckDBQueryProgress -> !Word64
+ Database.DuckDB.FFI: [duckDBQueryProgressTotalRows] :: DuckDBQueryProgress -> Word64
- Database.DuckDB.FFI: [duckDBResultDeprecatedColumnCount] :: DuckDBResult -> !DuckDBIdx
+ Database.DuckDB.FFI: [duckDBResultDeprecatedColumnCount] :: DuckDBResult -> DuckDBIdx
- Database.DuckDB.FFI: [duckDBResultDeprecatedColumns] :: DuckDBResult -> !Ptr DuckDBColumn
+ Database.DuckDB.FFI: [duckDBResultDeprecatedColumns] :: DuckDBResult -> Ptr DuckDBColumn
- Database.DuckDB.FFI: [duckDBResultDeprecatedErrorMessage] :: DuckDBResult -> !CString
+ Database.DuckDB.FFI: [duckDBResultDeprecatedErrorMessage] :: DuckDBResult -> CString
- Database.DuckDB.FFI: [duckDBResultDeprecatedRowCount] :: DuckDBResult -> !DuckDBIdx
+ Database.DuckDB.FFI: [duckDBResultDeprecatedRowCount] :: DuckDBResult -> DuckDBIdx
- Database.DuckDB.FFI: [duckDBResultDeprecatedRowsChanged] :: DuckDBResult -> !DuckDBIdx
+ Database.DuckDB.FFI: [duckDBResultDeprecatedRowsChanged] :: DuckDBResult -> DuckDBIdx
- Database.DuckDB.FFI: [duckDBResultInternalData] :: DuckDBResult -> !Ptr ()
+ Database.DuckDB.FFI: [duckDBResultInternalData] :: DuckDBResult -> Ptr ()
- Database.DuckDB.FFI: [duckDBStringData] :: DuckDBString -> !Ptr CChar
+ Database.DuckDB.FFI: [duckDBStringData] :: DuckDBString -> Ptr CChar
- Database.DuckDB.FFI: [duckDBStringSize] :: DuckDBString -> !DuckDBIdx
+ Database.DuckDB.FFI: [duckDBStringSize] :: DuckDBString -> DuckDBIdx
- Database.DuckDB.FFI: [duckDBTimeStructHour] :: DuckDBTimeStruct -> !Int8
+ Database.DuckDB.FFI: [duckDBTimeStructHour] :: DuckDBTimeStruct -> Int8
- Database.DuckDB.FFI: [duckDBTimeStructMicros] :: DuckDBTimeStruct -> !Int32
+ Database.DuckDB.FFI: [duckDBTimeStructMicros] :: DuckDBTimeStruct -> Int32
- Database.DuckDB.FFI: [duckDBTimeStructMinute] :: DuckDBTimeStruct -> !Int8
+ Database.DuckDB.FFI: [duckDBTimeStructMinute] :: DuckDBTimeStruct -> Int8
- Database.DuckDB.FFI: [duckDBTimeStructSecond] :: DuckDBTimeStruct -> !Int8
+ Database.DuckDB.FFI: [duckDBTimeStructSecond] :: DuckDBTimeStruct -> Int8
- Database.DuckDB.FFI: [duckDBTimeTzStructOffset] :: DuckDBTimeTzStruct -> !Int32
+ Database.DuckDB.FFI: [duckDBTimeTzStructOffset] :: DuckDBTimeTzStruct -> Int32
- Database.DuckDB.FFI: [duckDBTimeTzStructTime] :: DuckDBTimeTzStruct -> !DuckDBTimeStruct
+ Database.DuckDB.FFI: [duckDBTimeTzStructTime] :: DuckDBTimeTzStruct -> DuckDBTimeStruct
- Database.DuckDB.FFI: [duckDBTimestampStructDate] :: DuckDBTimestampStruct -> !DuckDBDateStruct
+ Database.DuckDB.FFI: [duckDBTimestampStructDate] :: DuckDBTimestampStruct -> DuckDBDateStruct
- Database.DuckDB.FFI: [duckDBTimestampStructTime] :: DuckDBTimestampStruct -> !DuckDBTimeStruct
+ Database.DuckDB.FFI: [duckDBTimestampStructTime] :: DuckDBTimestampStruct -> DuckDBTimeStruct
- Database.DuckDB.FFI: [duckDBUHugeIntLower] :: DuckDBUHugeInt -> !Word64
+ Database.DuckDB.FFI: [duckDBUHugeIntLower] :: DuckDBUHugeInt -> Word64
- Database.DuckDB.FFI: [duckDBUHugeIntUpper] :: DuckDBUHugeInt -> !Word64
+ Database.DuckDB.FFI: [duckDBUHugeIntUpper] :: DuckDBUHugeInt -> Word64

Files

CHANGELOG.md view
@@ -1,5 +1,34 @@ # Changelog +## 1.5.3.0++- Export invokers for Arrow schema/array release and all four stream callbacks.+  Applications can now call the function pointers exposed by the Arrow types+  without repeating foreign declarations from the test suite.+- Export the corresponding `wrapArrow...` constructors for Haskell callbacks.+  Add `releaseArrowSchema`, `releaseArrowArray`, and `releaseArrowStream` to+  release initialized objects only when their release callback is non-null.+  These helpers mask asynchronous exceptions during release. Callers still+  own the outer struct storage and any callback pointers they allocate.++- Deprecated native modules now emit a Haskell deprecation warning. The bindings+  remain available for applications that need the legacy API.++- Previously, temporal foreign imports treated single-field C structs as scalar+  arguments and return values. C adapters now perform those conversions with+  the native calling convention. The typed Haskell signatures retain every+  native field, including DATE days and packed TIME_TZ bits.+- Previously, deprecated Arrow handles were treated as wrappers containing an+  internal pointer. The helpers now use the actual Arrow object address and+  clear only its release callback after a move. This prevents corruption of+  schema fields, array metadata, and stream callbacks. Tests use real DuckDB+  objects and verify that moved buffers remain valid.+- Download the native library to the user cache and verify the archive's SHA256 checksum.+- Use an existing native library when the user or Nix supplies it.+- Track native library selection through the `systemlib` flag, `extra-lib-dirs`, and the `--duckdb-install-dir` configure option.+- Raise the minimum native DuckDB version to 1.5.3.+- Use GHC 9.14.1 by default. Test the latest stable patch release in each GHC series from 9.6 to 9.14.+ ## 1.5.0.0 - Upgrade the vendored DuckDB C header and build metadata to DuckDB 1.5.0, making `duckdb-ffi` a DuckDB `1.5.0+` binding set. - Add raw FFI coverage for new DuckDB 1.5 API areas including custom config options, scalar function init/state hooks, copy functions, file-system handles, catalog inspection, logging, and new helper/appender/vector APIs.
Setup.hs view
@@ -1,3 +1,134 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ScopedTypeVariables #-}++import Control.Exception (IOException, catch, throwIO)+import Control.Monad (filterM, unless, when)+import Data.List (intercalate, isPrefixOf, nub, stripPrefix)+import Data.Maybe (isJust, isNothing)+import Distribution.PackageDescription import Distribution.Simple+import Distribution.Simple.Setup (ConfigFlags, configConfigurationsFlags, configConfigureArgs, configExtraLibDirs)+#if MIN_VERSION_Cabal(3,14,0)+import Distribution.Utils.Path (getSymbolicPath, makeSymbolicPath)+#endif+import System.Directory+import System.Environment (lookupEnv)+import System.FilePath (isAbsolute, (</>))+import System.IO.Temp (withTempDirectory)+import System.Info (arch, os)+import System.Process (callProcess, readProcess) -main = defaultMain+#if !MIN_VERSION_Cabal(3,14,0)+-- | Cabal versions before 3.14 use plain file paths.+makeSymbolicPath :: FilePath -> FilePath+makeSymbolicPath = id++-- | Read a plain file path with Cabal versions before 3.14.+getSymbolicPath :: FilePath -> FilePath+getSymbolicPath = id+#endif++-- | Save the native library directory in the package description.+main :: IO ()+main = defaultMainWithHooks simpleUserHooks{confHook = configure}+  where+    configure (description, hooks) flags = do+        nativeDirs <- nativeLibraryDirs flags+        let addLibrary lib =+                let info = libBuildInfo lib+                 in lib+                        { libBuildInfo =+                            info+                                { ldOptions =+                                    (if os `elem` ["linux", "darwin"] then concatMap (\dir -> ["-Xlinker", "-rpath", "-Xlinker", dir]) nativeDirs else [])+                                        <> ldOptions info+                                }+                        }+            updated = description{condLibrary = fmap (fmap addLibrary) (condLibrary description)}+            updatedFlags =+                if null nativeDirs+                    then flags+                    else flags{configExtraLibDirs = map makeSymbolicPath nativeDirs}+        confHook simpleUserHooks (updated, hooks) updatedFlags++-- | Use a supplied library, or download one to the user cache.+nativeLibraryDirs :: ConfigFlags -> IO [FilePath]+nativeLibraryDirs flags = do+    nixBuild <- lookupEnv "NIX_BUILD_TOP"+    nixShell <- lookupEnv "IN_NIX_SHELL"+    let supplied = map getSymbolicPath (configExtraLibDirs flags)+        system = lookupFlagAssignment (mkFlagName "systemlib") (configConfigurationsFlags flags) == Just True+    if isJust nixBuild || isJust nixShell+        then pure []+        else+            if system || not (null supplied)+                then do+                    unless (all isAbsolute supplied) $+                        fail "Use absolute paths in --extra-lib-dirs so Cabal can track the library location."+                    pure (nub supplied)+                else (: []) <$> installNativeLibrary flags++{- | Pin the download to the release verified by the checksums below.+The Haskell package version can differ from the native library version.+-}+nativeVersion :: String+nativeVersion = "1.5.3"++-- | Select an official archive and its release checksum.+nativeArchive :: IO (String, String, FilePath)+nativeArchive = case (os, arch) of+    ("linux", "x86_64") -> pure ("linux-amd64", "0a926eba5bce0abc0010f4b9109133e4440cb74e97bd10fd2d0fc2a721621b05", "libduckdb.so")+    ("linux", "aarch64") -> pure ("linux-arm64", "162806d591c0431d031d9bdf43dbecc5f00755da01a2064df68f9a69a6f50a10", "libduckdb.so")+    ("darwin", "x86_64") -> mac+    ("darwin", "aarch64") -> mac+    _ -> fail "Automatic DuckDB installation supports glibc Linux and macOS. Supply DuckDB >= 1.5.3 and < 1.6 with -fsystemlib and --extra-lib-dirs."+  where+    mac = pure ("osx-universal", "386f8e8b3b4bc8d128762327121e22065ce45f2ee55ef1b1f412ce11e0e6c51f", "libduckdb.dylib")++-- | Download into a temporary directory. Publish the verified library atomically.+installNativeLibrary :: ConfigFlags -> IO FilePath+installNativeLibrary flags = do+    base <- case filter (isPrefixOf "--duckdb-install-dir") (configConfigureArgs flags) of+        [] -> getXdgDirectory XdgCache "duckdb-haskell" >>= makeAbsolute+        [option]+            | Just directory <- stripPrefix "--duckdb-install-dir=" option+            , isAbsolute directory ->+                pure directory+        _ -> fail "Pass --configure-option=--duckdb-install-dir=/absolute/path once, with an absolute directory."+    (platform, checksum, libraryName) <- nativeArchive+    let root = base </> nativeVersion+        destination = root </> platform+        complete dir = do+            libraryExists <- doesFileExist (dir </> libraryName)+            markerExists <- doesFileExist (dir </> "SHA256")+            marker <- if markerExists then readFile (dir </> "SHA256") else pure ""+            pure (libraryExists && marker == checksum <> "\n")+    -- Check existence first to accept a library installed by another build.+    exists <- doesPathExist destination+    ready <- complete destination+    unless ready $ do+        when exists $ fail ("Incomplete DuckDB installation: remove " <> destination <> " and retry.")+        let programs = ["curl", "unzip", if os == "darwin" then "shasum" else "sha256sum"]+        missing <- filterM (\program -> isNothing <$> findExecutable program) programs+        unless (null missing) $+            fail ("DuckDB installation requires these programs on PATH: " <> intercalate ", " missing <> ". Install them, or supply DuckDB with -fsystemlib and --extra-lib-dirs. See README.md for details.")+        createDirectoryIfMissing True root+        withTempDirectory root ".download-" $ \staging -> do+            let archive = staging </> "duckdb.zip"+                extracted = staging </> "library"+                url = "https://github.com/duckdb/duckdb/releases/download/v" <> nativeVersion <> "/libduckdb-" <> platform <> ".zip"+            putStrLn ("Downloading DuckDB " <> nativeVersion <> " to " <> destination)+            callProcess "curl" ["--fail", "--location", "--proto", "=https", "--proto-redir", "=https", "--tlsv1.2", "--retry", "2", "--output", archive, url]+            digest <-+                if os == "darwin"+                    then readProcess "shasum" ["-a", "256", archive] ""+                    else readProcess "sha256sum" [archive] ""+            unless (takeWhile (/= ' ') digest == checksum) $ fail "DuckDB archive checksum mismatch."+            createDirectory extracted+            callProcess "unzip" ["-q", archive, libraryName, "-d", extracted]+            writeFile (extracted </> "SHA256") (checksum <> "\n")+            -- Another build can publish the same verified archive first.+            renameDirectory extracted destination `catch` \(err :: IOException) -> do+                installed <- complete destination+                unless installed (throwIO err)+    pure destination
+ cbits/duckdb_arrow.h view
@@ -0,0 +1,40 @@+#ifndef DUCKDB_HASKELL_ARROW_H+#define DUCKDB_HASKELL_ARROW_H++#include <stdint.h>++// Arrow C Data Interface structures used by the deprecated DuckDB API.+struct ArrowSchema {+  const char *format;+  const char *name;+  const char *metadata;+  int64_t flags;+  int64_t n_children;+  struct ArrowSchema **children;+  struct ArrowSchema *dictionary;+  void (*release)(struct ArrowSchema *);+  void *private_data;+};++struct ArrowArray {+  int64_t length;+  int64_t null_count;+  int64_t offset;+  int64_t n_buffers;+  int64_t n_children;+  const void **buffers;+  struct ArrowArray **children;+  struct ArrowArray *dictionary;+  void (*release)(struct ArrowArray *);+  void *private_data;+};++struct ArrowArrayStream {+  int (*get_schema)(struct ArrowArrayStream *, struct ArrowSchema *);+  int (*get_next)(struct ArrowArrayStream *, struct ArrowArray *);+  const char *(*get_last_error)(struct ArrowArrayStream *);+  void (*release)(struct ArrowArrayStream *);+  void *private_data;+};++#endif
cbits/duckdb_stub.c view
@@ -1,4 +1,5 @@ #include "duckdb.h"+#include "duckdb_arrow.h" #include <stdint.h>  // duckdb_query_progress returns a struct by value; expose a pointer-based version@@ -154,46 +155,43 @@   duckdb_result_arrow_array(*result, chunk, out_array); } -void *wrapped_duckdb_arrow_schema_internal_ptr(duckdb_arrow_schema schema) {-  if (!schema) {-    return NULL;-  }-  return schema->internal_ptr;+// Deprecated Arrow handles point directly to Arrow C Data Interface structures.+void *wrapped_duckdb_arrow_schema_internal_ptr(duckdb_arrow_schema handle) {+  struct ArrowSchema *value = (struct ArrowSchema *)handle;+  return value && value->release ? value : NULL; } -void wrapped_duckdb_arrow_schema_clear_internal_ptr(duckdb_arrow_schema schema) {-  if (!schema) {-    return;+void wrapped_duckdb_arrow_schema_clear_internal_ptr(duckdb_arrow_schema handle) {+  struct ArrowSchema *value = (struct ArrowSchema *)handle;+  if (value) {+    value->release = NULL;   }-  schema->internal_ptr = NULL; } -void *wrapped_duckdb_arrow_array_internal_ptr(duckdb_arrow_array array) {-  if (!array) {-    return NULL;-  }-  return array->internal_ptr;+// Deprecated Arrow handles point directly to Arrow C Data Interface structures.+void *wrapped_duckdb_arrow_array_internal_ptr(duckdb_arrow_array handle) {+  struct ArrowArray *value = (struct ArrowArray *)handle;+  return value && value->release ? value : NULL; } -void wrapped_duckdb_arrow_array_clear_internal_ptr(duckdb_arrow_array array) {-  if (!array) {-    return;+void wrapped_duckdb_arrow_array_clear_internal_ptr(duckdb_arrow_array handle) {+  struct ArrowArray *value = (struct ArrowArray *)handle;+  if (value) {+    value->release = NULL;   }-  array->internal_ptr = NULL; } -void *wrapped_duckdb_arrow_stream_internal_ptr(duckdb_arrow_stream stream) {-  if (!stream) {-    return NULL;-  }-  return stream->internal_ptr;+// Deprecated Arrow handles point directly to Arrow C Data Interface structures.+void *wrapped_duckdb_arrow_stream_internal_ptr(duckdb_arrow_stream handle) {+  struct ArrowArrayStream *value = (struct ArrowArrayStream *)handle;+  return value && value->release ? value : NULL; } -void wrapped_duckdb_arrow_stream_clear_internal_ptr(duckdb_arrow_stream stream) {-  if (!stream) {-    return;+void wrapped_duckdb_arrow_stream_clear_internal_ptr(duckdb_arrow_stream handle) {+  struct ArrowArrayStream *value = (struct ArrowArrayStream *)handle;+  if (value) {+    value->release = NULL;   }-  stream->internal_ptr = NULL; }  // duckdb_stream_fetch_chunk takes duckdb_result by value; provide a pointer form@@ -466,64 +464,64 @@  // duckdb_from_date returns a struct by value; emit the result through an // out-parameter to avoid struct-return plumbing on the Haskell side.-void wrapped_duckdb_from_date(duckdb_date date, duckdb_date_struct *out_value) {+void wrapped_duckdb_from_date(int32_t date, duckdb_date_struct *out_value) {   if (!out_value) {     return;   }-  *out_value = duckdb_from_date(date);+  *out_value = duckdb_from_date((duckdb_date){date}); }  // duckdb_to_date expects a struct by value; copy from pointer so NULL can be passed // and so the ABI stays simple.-duckdb_date wrapped_duckdb_to_date(const duckdb_date_struct *input) {+int32_t wrapped_duckdb_to_date(const duckdb_date_struct *input) {   duckdb_date_struct tmp = {0};   if (input) {     tmp = *input;   }-  return duckdb_to_date(tmp);+  return duckdb_to_date(tmp).days; }  // duckdb_from_time returns a struct by value; wrap to emit via an out pointer.-void wrapped_duckdb_from_time(duckdb_time time, duckdb_time_struct *out_value) {+void wrapped_duckdb_from_time(int64_t time, duckdb_time_struct *out_value) {   if (!out_value) {     return;   }-  *out_value = duckdb_from_time(time);+  *out_value = duckdb_from_time((duckdb_time){time}); }  // duckdb_from_time_tz returns a struct by value; provide pointer-based output.-void wrapped_duckdb_from_time_tz(duckdb_time_tz micros, duckdb_time_tz_struct *out_value) {+void wrapped_duckdb_from_time_tz(uint64_t micros, duckdb_time_tz_struct *out_value) {   if (!out_value) {     return;   }-  *out_value = duckdb_from_time_tz(micros);+  *out_value = duckdb_from_time_tz((duckdb_time_tz){micros}); }  // duckdb_to_time expects a struct by value; copy from pointer for the bindings.-duckdb_time wrapped_duckdb_to_time(const duckdb_time_struct *input) {+int64_t wrapped_duckdb_to_time(const duckdb_time_struct *input) {   duckdb_time_struct tmp = {0};   if (input) {     tmp = *input;   }-  return duckdb_to_time(tmp);+  return duckdb_to_time(tmp).micros; }  // duckdb_from_timestamp returns a struct by value; write into caller-provided // storage instead.-void wrapped_duckdb_from_timestamp(duckdb_timestamp ts, duckdb_timestamp_struct *out_value) {+void wrapped_duckdb_from_timestamp(int64_t ts, duckdb_timestamp_struct *out_value) {   if (!out_value) {     return;   }-  *out_value = duckdb_from_timestamp(ts);+  *out_value = duckdb_from_timestamp((duckdb_timestamp){ts}); }  // duckdb_to_timestamp expects a struct by value; copy from pointer before calling.-duckdb_timestamp wrapped_duckdb_to_timestamp(const duckdb_timestamp_struct *input) {+int64_t wrapped_duckdb_to_timestamp(const duckdb_timestamp_struct *input) {   duckdb_timestamp_struct tmp = {0};   if (input) {     tmp = *input;   }-  return duckdb_to_timestamp(tmp);+  return duckdb_to_timestamp(tmp).micros; }  // duckdb_uhugeint_to_double consumes its argument by value; copy from pointer to@@ -543,4 +541,174 @@     return;   }   *out_value = duckdb_double_to_uhugeint(input);+}++// Convert scalar fields at the C ABI boundary.+bool wrapped_duckdb_is_finite_date(int32_t date) {+  return duckdb_is_finite_date((duckdb_date){date});+}++// Convert scalar fields at the C ABI boundary.+uint64_t wrapped_duckdb_create_time_tz(int64_t micros, int32_t offset) {+  return duckdb_create_time_tz(micros, offset).bits;+}++// Convert scalar fields at the C ABI boundary.+bool wrapped_duckdb_is_finite_timestamp(int64_t ts) {+  return duckdb_is_finite_timestamp((duckdb_timestamp){ts});+}++// Convert scalar fields at the C ABI boundary.+bool wrapped_duckdb_is_finite_timestamp_s(int64_t ts) {+  return duckdb_is_finite_timestamp_s((duckdb_timestamp_s){ts});+}++// Convert scalar fields at the C ABI boundary.+bool wrapped_duckdb_is_finite_timestamp_ms(int64_t ts) {+  return duckdb_is_finite_timestamp_ms((duckdb_timestamp_ms){ts});+}++// Convert scalar fields at the C ABI boundary.+bool wrapped_duckdb_is_finite_timestamp_ns(int64_t ts) {+  return duckdb_is_finite_timestamp_ns((duckdb_timestamp_ns){ts});+}++// Convert scalar fields at the C ABI boundary.+duckdb_state wrapped_duckdb_bind_date(duckdb_prepared_statement prepared_statement, idx_t param_idx, int32_t val) {+  return duckdb_bind_date(prepared_statement, param_idx, (duckdb_date){val});+}++// Convert scalar fields at the C ABI boundary.+duckdb_state wrapped_duckdb_bind_time(duckdb_prepared_statement prepared_statement, idx_t param_idx, int64_t val) {+  return duckdb_bind_time(prepared_statement, param_idx, (duckdb_time){val});+}++// Convert scalar fields at the C ABI boundary.+duckdb_state wrapped_duckdb_bind_timestamp(duckdb_prepared_statement prepared_statement, idx_t param_idx, int64_t val) {+  return duckdb_bind_timestamp(prepared_statement, param_idx, (duckdb_timestamp){val});+}++// Convert scalar fields at the C ABI boundary.+duckdb_state wrapped_duckdb_bind_timestamp_tz(duckdb_prepared_statement prepared_statement, idx_t param_idx, int64_t val) {+  return duckdb_bind_timestamp_tz(prepared_statement, param_idx, (duckdb_timestamp){val});+}++// Convert scalar fields at the C ABI boundary.+duckdb_value wrapped_duckdb_create_date(int32_t input) {+  return duckdb_create_date((duckdb_date){input});+}++// Convert scalar fields at the C ABI boundary.+duckdb_value wrapped_duckdb_create_time(int64_t input) {+  return duckdb_create_time((duckdb_time){input});+}++// Convert scalar fields at the C ABI boundary.+duckdb_value wrapped_duckdb_create_time_ns(int64_t input) {+  return duckdb_create_time_ns((duckdb_time_ns){input});+}++// Convert scalar fields at the C ABI boundary.+duckdb_value wrapped_duckdb_create_time_tz_value(uint64_t value) {+  return duckdb_create_time_tz_value((duckdb_time_tz){value});+}++// Convert scalar fields at the C ABI boundary.+duckdb_value wrapped_duckdb_create_timestamp(int64_t input) {+  return duckdb_create_timestamp((duckdb_timestamp){input});+}++// Convert scalar fields at the C ABI boundary.+duckdb_value wrapped_duckdb_create_timestamp_tz(int64_t input) {+  return duckdb_create_timestamp_tz((duckdb_timestamp){input});+}++// Convert scalar fields at the C ABI boundary.+duckdb_value wrapped_duckdb_create_timestamp_s(int64_t input) {+  return duckdb_create_timestamp_s((duckdb_timestamp_s){input});+}++// Convert scalar fields at the C ABI boundary.+duckdb_value wrapped_duckdb_create_timestamp_ms(int64_t input) {+  return duckdb_create_timestamp_ms((duckdb_timestamp_ms){input});+}++// Convert scalar fields at the C ABI boundary.+duckdb_value wrapped_duckdb_create_timestamp_ns(int64_t input) {+  return duckdb_create_timestamp_ns((duckdb_timestamp_ns){input});+}++// Convert scalar fields at the C ABI boundary.+int32_t wrapped_duckdb_get_date(duckdb_value val) {+  return duckdb_get_date(val).days;+}++// Convert scalar fields at the C ABI boundary.+int64_t wrapped_duckdb_get_time(duckdb_value val) {+  return duckdb_get_time(val).micros;+}++// Convert scalar fields at the C ABI boundary.+int64_t wrapped_duckdb_get_time_ns(duckdb_value val) {+  return duckdb_get_time_ns(val).nanos;+}++// Convert scalar fields at the C ABI boundary.+uint64_t wrapped_duckdb_get_time_tz(duckdb_value val) {+  return duckdb_get_time_tz(val).bits;+}++// Convert scalar fields at the C ABI boundary.+int64_t wrapped_duckdb_get_timestamp(duckdb_value val) {+  return duckdb_get_timestamp(val).micros;+}++// Convert scalar fields at the C ABI boundary.+int64_t wrapped_duckdb_get_timestamp_tz(duckdb_value val) {+  return duckdb_get_timestamp_tz(val).micros;+}++// Convert scalar fields at the C ABI boundary.+int64_t wrapped_duckdb_get_timestamp_s(duckdb_value val) {+  return duckdb_get_timestamp_s(val).seconds;+}++// Convert scalar fields at the C ABI boundary.+int64_t wrapped_duckdb_get_timestamp_ms(duckdb_value val) {+  return duckdb_get_timestamp_ms(val).millis;+}++// Convert scalar fields at the C ABI boundary.+int64_t wrapped_duckdb_get_timestamp_ns(duckdb_value val) {+  return duckdb_get_timestamp_ns(val).nanos;+}++// Convert scalar fields at the C ABI boundary.+duckdb_state wrapped_duckdb_append_date(duckdb_appender appender, int32_t value) {+  return duckdb_append_date(appender, (duckdb_date){value});+}++// Convert scalar fields at the C ABI boundary.+duckdb_state wrapped_duckdb_append_time(duckdb_appender appender, int64_t value) {+  return duckdb_append_time(appender, (duckdb_time){value});+}++// Convert scalar fields at the C ABI boundary.+duckdb_state wrapped_duckdb_append_timestamp(duckdb_appender appender, int64_t value) {+  return duckdb_append_timestamp(appender, (duckdb_timestamp){value});+}++// Convert scalar fields at the C ABI boundary.+int32_t wrapped_duckdb_value_date(duckdb_result *result, idx_t col, idx_t row) {+  return duckdb_value_date(result, col, row).days;+}++// Convert scalar fields at the C ABI boundary.+int64_t wrapped_duckdb_value_time(duckdb_result *result, idx_t col, idx_t row) {+  return duckdb_value_time(result, col, row).micros;+}++// Convert scalar fields at the C ABI boundary.+int64_t wrapped_duckdb_value_timestamp(duckdb_result *result, idx_t col, idx_t row) {+  return duckdb_value_timestamp(result, col, row).micros; }
duckdb-ffi.cabal view
@@ -1,37 +1,52 @@ cabal-version: 3.4 name: duckdb-ffi-version: 1.5.0.0+version: 1.5.3.0 author: Matthias Pall Gissurarson maintainer: mpg@mpg.is category: Database-build-type: Simple+build-type: Custom tested-with:-  ghc ==9.6.*-  ghc ==9.8.*-  ghc ==9.10.*-  ghc ==9.12.*-  ghc ==9.14.*+  ghc ==9.10.3+  ghc ==9.12.4+  ghc ==9.14.1+  ghc ==9.6.7+  ghc ==9.8.4  license: MPL-2.0 license-file: LICENSE-synopsis: Haskell FFI bindings for DuckDB+synopsis: Low-level bindings to the DuckDB C API homepage: https://github.com/Tritlo/duckdb-haskell bug-reports: https://github.com/Tritlo/duckdb-haskell/issues description:-  This library provides low-level FFI bindings to DuckDB.-  Tested with DuckDB version 1.5.0.-  Exposes the full DuckDB C API.+  Haskell FFI bindings to the DuckDB C API.+  Supports native DuckDB >= 1.5.3 and < 1.6. Tested with version 1.5.3.   .-  Requires libduckdb to be installed on the system.-  See <https://duckdb.org/docs/installation> for installation instructions.+  Downloads a verified native library to the user cache on Linux and macOS.+  Uses an existing library when the user or Nix supplies it.  extra-doc-files: CHANGELOG.md+extra-source-files: cbits/duckdb_arrow.h  source-repository head   type: git   location: https://github.com/Tritlo/duckdb-haskell.git   subdir: duckdb-ffi +-- Setup.hs reads this flag. cabal check reports it as unused.+flag systemlib+  description: Use an installed DuckDB library without downloading it.+  default: False+  manual: True++custom-setup+  setup-depends:+    base >=4.18 && <4.23,+    Cabal >=3.10 && <3.19,+    directory >=1.3 && <1.4,+    filepath >=1.4 && <1.6,+    process >=1.6 && <1.7,+    temporary >=1.3 && <1.4,+ library   default-language:     Haskell2010@@ -113,10 +128,6 @@     text >=2.0 && <2.2,     time >=1.12 && <1.16,     transformers >=0.6 && <0.7,--  if os(linux)-    ghc-options: "-optl-Wl,-rpath,'$ORIGIN/../cbits/duckdb'"-    ld-options: "-Wl,-rpath,'$ORIGIN/../cbits/duckdb'"  test-suite duckdb-ffi-tests   type: exitcode-stdio-1.0
src/Database/DuckDB/FFI/Appender.hs view
@@ -320,15 +320,15 @@     c_duckdb_append_double :: DuckDBAppender -> CDouble -> IO DuckDBState  -- | Append a duckdb_date value to the appender.-foreign import ccall "duckdb_append_date"+foreign import ccall "wrapped_duckdb_append_date"     c_duckdb_append_date :: DuckDBAppender -> DuckDBDate -> IO DuckDBState  -- | Append a duckdb_time value to the appender.-foreign import ccall "duckdb_append_time"+foreign import ccall "wrapped_duckdb_append_time"     c_duckdb_append_time :: DuckDBAppender -> DuckDBTime -> IO DuckDBState  -- | Append a duckdb_timestamp value to the appender.-foreign import ccall "duckdb_append_timestamp"+foreign import ccall "wrapped_duckdb_append_timestamp"     c_duckdb_append_timestamp :: DuckDBAppender -> DuckDBTimestamp -> IO DuckDBState  {- | Append a duckdb_interval value to the appender.
src/Database/DuckDB/FFI/Arrow.hs view
@@ -1,14 +1,41 @@+{- | Arrow C Data Interface conversion and callbacks.++The @mkArrow...@ functions invoke existing callback pointers. The+@wrapArrow...@ functions allocate callback pointers for Haskell functions.+Keep each allocated pointer alive while native code can call it. Free it with+@freeHaskellFunPtr@ after its last use. Catch all callback exceptions before+they can return to C.+-} module Database.DuckDB.FFI.Arrow (     c_duckdb_to_arrow_schema,     c_duckdb_data_chunk_to_arrow,     c_duckdb_schema_from_arrow,     c_duckdb_data_chunk_from_arrow,     c_duckdb_destroy_arrow_converted_schema,+    mkArrowSchemaRelease,+    mkArrowArrayRelease,+    mkArrowStreamGetSchema,+    mkArrowStreamGetNext,+    mkArrowStreamGetLastError,+    mkArrowStreamRelease,+    wrapArrowSchemaRelease,+    wrapArrowArrayRelease,+    wrapArrowStreamGetSchema,+    wrapArrowStreamGetNext,+    wrapArrowStreamGetLastError,+    wrapArrowStreamRelease,+    releaseArrowSchema,+    releaseArrowArray,+    releaseArrowStream, ) where +import Control.Exception (mask_)+import Control.Monad (when) import Database.DuckDB.FFI.Types import Foreign.C.String (CString)-import Foreign.Ptr (Ptr)+import Foreign.C.Types (CInt (..))+import Foreign.Ptr (FunPtr, Ptr, nullFunPtr)+import Foreign.Storable (peek)  {- | Transforms a DuckDB Schema into an Arrow Schema @@ -78,3 +105,81 @@ -} foreign import ccall safe "duckdb_destroy_arrow_converted_schema"     c_duckdb_destroy_arrow_converted_schema :: Ptr DuckDBArrowConvertedSchema -> IO ()++-- | Invoke a non-null Arrow schema release callback.+foreign import ccall safe "dynamic"+    mkArrowSchemaRelease :: FunPtr (Ptr ArrowSchema -> IO ()) -> Ptr ArrowSchema -> IO ()++-- | Invoke a non-null Arrow array release callback.+foreign import ccall safe "dynamic"+    mkArrowArrayRelease :: FunPtr (Ptr ArrowArray -> IO ()) -> Ptr ArrowArray -> IO ()++-- | Invoke the schema callback of an unreleased Arrow stream.+foreign import ccall safe "dynamic"+    mkArrowStreamGetSchema :: FunPtr (Ptr ArrowArrayStream -> Ptr ArrowSchema -> IO CInt) -> Ptr ArrowArrayStream -> Ptr ArrowSchema -> IO CInt++-- | Invoke the next-array callback of an unreleased Arrow stream.+foreign import ccall safe "dynamic"+    mkArrowStreamGetNext :: FunPtr (Ptr ArrowArrayStream -> Ptr ArrowArray -> IO CInt) -> Ptr ArrowArrayStream -> Ptr ArrowArray -> IO CInt++-- | Read a stream error after a failed callback. Copy it before the next callback.+foreign import ccall safe "dynamic"+    mkArrowStreamGetLastError :: FunPtr (Ptr ArrowArrayStream -> IO CString) -> Ptr ArrowArrayStream -> IO CString++-- | Invoke a non-null Arrow stream release callback.+foreign import ccall safe "dynamic"+    mkArrowStreamRelease :: FunPtr (Ptr ArrowArrayStream -> IO ()) -> Ptr ArrowArrayStream -> IO ()++-- | Allocate a C-callable Arrow schema release callback.+foreign import ccall "wrapper"+    wrapArrowSchemaRelease :: (Ptr ArrowSchema -> IO ()) -> IO (FunPtr (Ptr ArrowSchema -> IO ()))++-- | Allocate a C-callable Arrow array release callback.+foreign import ccall "wrapper"+    wrapArrowArrayRelease :: (Ptr ArrowArray -> IO ()) -> IO (FunPtr (Ptr ArrowArray -> IO ()))++-- | Allocate a C-callable Arrow stream schema callback.+foreign import ccall "wrapper"+    wrapArrowStreamGetSchema :: (Ptr ArrowArrayStream -> Ptr ArrowSchema -> IO CInt) -> IO (FunPtr (Ptr ArrowArrayStream -> Ptr ArrowSchema -> IO CInt))++-- | Allocate a C-callable Arrow stream next-array callback.+foreign import ccall "wrapper"+    wrapArrowStreamGetNext :: (Ptr ArrowArrayStream -> Ptr ArrowArray -> IO CInt) -> IO (FunPtr (Ptr ArrowArrayStream -> Ptr ArrowArray -> IO CInt))++-- | Allocate a C-callable Arrow stream last-error callback.+foreign import ccall "wrapper"+    wrapArrowStreamGetLastError :: (Ptr ArrowArrayStream -> IO CString) -> IO (FunPtr (Ptr ArrowArrayStream -> IO CString))++-- | Allocate a C-callable Arrow stream release callback.+foreign import ccall "wrapper"+    wrapArrowStreamRelease :: (Ptr ArrowArrayStream -> IO ()) -> IO (FunPtr (Ptr ArrowArrayStream -> IO ()))++{- | Release an Arrow schema unless its release callback is null.+The pointer must address an initialized structure. The structure itself stays+allocated. Asynchronous exceptions are masked during release.+-}+releaseArrowSchema :: Ptr ArrowSchema -> IO ()+releaseArrowSchema ptr = mask_ $ do+    schema <- peek ptr+    when (arrowSchemaRelease schema /= nullFunPtr) $+        mkArrowSchemaRelease (arrowSchemaRelease schema) ptr++{- | Release an Arrow array unless its release callback is null.+The pointer must address an initialized structure. The structure itself stays+allocated. Asynchronous exceptions are masked during release.+-}+releaseArrowArray :: Ptr ArrowArray -> IO ()+releaseArrowArray ptr = mask_ $ do+    array <- peek ptr+    when (arrowArrayRelease array /= nullFunPtr) $+        mkArrowArrayRelease (arrowArrayRelease array) ptr++{- | Release an Arrow stream unless its release callback is null.+The pointer must address an initialized structure. The structure itself stays+allocated. Asynchronous exceptions are masked during release.+-}+releaseArrowStream :: Ptr ArrowArrayStream -> IO ()+releaseArrowStream ptr = mask_ $ do+    stream <- peek ptr+    when (arrowStreamRelease stream /= nullFunPtr) $+        mkArrowStreamRelease (arrowStreamRelease stream) ptr
src/Database/DuckDB/FFI/BindValues.hs view
@@ -113,23 +113,23 @@     c_duckdb_bind_double :: DuckDBPreparedStatement -> DuckDBIdx -> CDouble -> IO DuckDBState  -- | Binds a duckdb_date value to the prepared statement at the specified index.-foreign import ccall safe "duckdb_bind_date"+foreign import ccall safe "wrapped_duckdb_bind_date"     c_duckdb_bind_date :: DuckDBPreparedStatement -> DuckDBIdx -> DuckDBDate -> IO DuckDBState  -- | Binds a duckdb_time value to the prepared statement at the specified index.-foreign import ccall safe "duckdb_bind_time"+foreign import ccall safe "wrapped_duckdb_bind_time"     c_duckdb_bind_time :: DuckDBPreparedStatement -> DuckDBIdx -> DuckDBTime -> IO DuckDBState  {- | Binds a duckdb_timestamp value to the prepared statement at the specified index. -}-foreign import ccall safe "duckdb_bind_timestamp"+foreign import ccall safe "wrapped_duckdb_bind_timestamp"     c_duckdb_bind_timestamp :: DuckDBPreparedStatement -> DuckDBIdx -> DuckDBTimestamp -> IO DuckDBState  {- | Binds a duckdb_timestamp value to the prepared statement at the specified index. -}-foreign import ccall safe "duckdb_bind_timestamp_tz"+foreign import ccall safe "wrapped_duckdb_bind_timestamp_tz"     c_duckdb_bind_timestamp_tz :: DuckDBPreparedStatement -> DuckDBIdx -> DuckDBTimestamp -> IO DuckDBState  {- | Binds a duckdb_interval value to the prepared statement at the specified
src/Database/DuckDB/FFI/Deprecated.hs view
@@ -1,3 +1,5 @@+{-# OPTIONS_GHC -Wno-deprecations #-}+ {- | Module      : Database.DuckDB.FFI.Deprecated Description : Re-exports of deprecated DuckDB C API bindings.@@ -6,7 +8,8 @@ so downstream code can opt-in to the legacy surface separately from the main @Database.DuckDB.FFI@ module. -}-module Database.DuckDB.FFI.Deprecated (+module Database.DuckDB.FFI.Deprecated+    {-# DEPRECATED "These DuckDB C APIs are deprecated upstream. Prefer Database.DuckDB.FFI and its nondeprecated modules." #-} (     module Database.DuckDB.FFI.Deprecated.Appender,     module Database.DuckDB.FFI.Deprecated.Arrow,     module Database.DuckDB.FFI.Deprecated.ExecutePrepared,
src/Database/DuckDB/FFI/Deprecated/Appender.hs view
@@ -1,4 +1,5 @@-module Database.DuckDB.FFI.Deprecated.Appender (+module Database.DuckDB.FFI.Deprecated.Appender+    {-# DEPRECATED "These DuckDB C APIs are deprecated upstream. Prefer Database.DuckDB.FFI and its nondeprecated modules." #-} (     c_duckdb_appender_error, ) where 
src/Database/DuckDB/FFI/Deprecated/Arrow.hs view
@@ -1,4 +1,5 @@-module Database.DuckDB.FFI.Deprecated.Arrow (+module Database.DuckDB.FFI.Deprecated.Arrow+    {-# DEPRECATED "These DuckDB C APIs are deprecated upstream. Prefer Database.DuckDB.FFI and its nondeprecated modules." #-} (     c_duckdb_query_arrow,     c_duckdb_query_arrow_schema,     c_duckdb_prepared_arrow_schema,@@ -45,7 +46,7 @@  Returns @DuckDBSuccess@ on success or @DuckDBError@ on failure. -}-foreign import ccall "duckdb_query_arrow"+foreign import ccall safe "duckdb_query_arrow"     c_duckdb_query_arrow :: DuckDBConnection -> CString -> Ptr DuckDBArrow -> IO DuckDBState  {- | > Warning Deprecation notice. This method is scheduled for removal in a future@@ -112,7 +113,7 @@  Returns @DuckDBSuccess@ on success or @DuckDBError@ on failure. -}-foreign import ccall "duckdb_query_arrow_array"+foreign import ccall safe "duckdb_query_arrow_array"     c_duckdb_query_arrow_array :: DuckDBArrow -> Ptr DuckDBArrowArray -> IO DuckDBState  {- | > Warning Deprecation notice. This method is scheduled for removal in a future@@ -209,7 +210,7 @@  Returns @DuckDBSuccess@ on success or @DuckDBError@ on failure. -}-foreign import ccall "duckdb_execute_prepared_arrow"+foreign import ccall safe "duckdb_execute_prepared_arrow"     c_duckdb_execute_prepared_arrow :: DuckDBPreparedStatement -> Ptr DuckDBArrow -> IO DuckDBState  {- | > Warning Deprecation notice. This method is scheduled for removal in a future@@ -224,7 +225,7 @@  Returns @DuckDBSuccess@ on success or @DuckDBError@ on failure. -}-foreign import ccall "duckdb_arrow_scan"+foreign import ccall safe "duckdb_arrow_scan"     c_duckdb_arrow_scan :: DuckDBConnection -> CString -> DuckDBArrowStream -> IO DuckDBState  {- | > Warning Deprecation notice. This method is scheduled for removal in a future@@ -244,7 +245,7 @@  Returns @DuckDBSuccess@ on success or @DuckDBError@ on failure. -}-foreign import ccall "duckdb_arrow_array_scan"+foreign import ccall safe "duckdb_arrow_array_scan"     c_duckdb_arrow_array_scan :: DuckDBConnection -> CString -> DuckDBArrowSchema -> DuckDBArrowArray -> Ptr DuckDBArrowStream -> IO DuckDBState  foreign import ccall safe "wrapped_duckdb_arrow_schema_internal_ptr"@@ -265,9 +266,8 @@ foreign import ccall safe "wrapped_duckdb_arrow_stream_clear_internal_ptr"     c_duckdb_arrow_stream_clear_internal_ptr :: DuckDBArrowStream -> IO () -{- | Read the @internal_ptr@ field of a deprecated Arrow schema wrapper.-Returns 'Nothing' when the wrapper is null or DuckDB has already cleared the-pointer.+{- | Return the Arrow schema address for a deprecated DuckDB handle.+Returns 'Nothing' when the handle is null or its release callback is null. -} duckdbArrowSchemaInternal :: DuckDBArrowSchema -> IO (Maybe ArrowSchemaPtr) duckdbArrowSchemaInternal schema = do@@ -277,15 +277,15 @@             then Nothing             else Just (ArrowSchemaPtr (castPtr raw)) -{- | Clear the @internal_ptr@ field on a deprecated Arrow schema wrapper.-Useful after copying the schema to application-managed storage.+{- | Set the Arrow schema release callback to null after a shallow copy.+The destination owns the buffers and must call its release callback.+This function does not release buffers or change other fields. -} duckdbArrowSchemaClear :: DuckDBArrowSchema -> IO () duckdbArrowSchemaClear = c_duckdb_arrow_schema_clear_internal_ptr -{- | Read the @internal_ptr@ field of a deprecated Arrow array wrapper.-Returns 'Nothing' when the wrapper is null or the internal pointer has been-cleared by DuckDB.+{- | Return the Arrow array address for a deprecated DuckDB handle.+Returns 'Nothing' when the handle is null or its release callback is null. -} duckdbArrowArrayInternal :: DuckDBArrowArray -> IO (Maybe ArrowArrayPtr) duckdbArrowArrayInternal array = do@@ -295,15 +295,15 @@             then Nothing             else Just (ArrowArrayPtr (castPtr raw)) -{- | Clear the @internal_ptr@ field on a deprecated Arrow array wrapper.-After this call DuckDB treats the wrapper as null.+{- | Set the Arrow array release callback to null after a shallow copy.+The destination owns the buffers and must call its release callback.+This function does not release buffers or change other fields. -} duckdbArrowArrayClear :: DuckDBArrowArray -> IO () duckdbArrowArrayClear = c_duckdb_arrow_array_clear_internal_ptr -{- | Read the @internal_ptr@ field of a deprecated Arrow stream wrapper.-Returns 'Nothing' when the wrapper is null or the stream has already been-released.+{- | Return the Arrow stream address for a deprecated DuckDB handle.+Returns 'Nothing' when the handle is null or its release callback is null. -} duckdbArrowStreamInternal :: DuckDBArrowStream -> IO (Maybe ArrowStreamPtr) duckdbArrowStreamInternal stream = do@@ -313,9 +313,9 @@             then Nothing             else Just (ArrowStreamPtr raw) -{- | Clear the @internal_ptr@ field on a deprecated Arrow stream wrapper.-Use this after consuming the stream via @duckdb_arrow_scan@ to avoid stale-pointers on the DuckDB side.+{- | Set the Arrow stream release callback to null after a shallow copy.+The destination owns the buffers and must call its release callback.+This function does not release buffers or change other fields. -} duckdbArrowStreamClear :: DuckDBArrowStream -> IO () duckdbArrowStreamClear = c_duckdb_arrow_stream_clear_internal_ptr
src/Database/DuckDB/FFI/Deprecated/ExecutePrepared.hs view
@@ -1,4 +1,5 @@-module Database.DuckDB.FFI.Deprecated.ExecutePrepared (+module Database.DuckDB.FFI.Deprecated.ExecutePrepared+    {-# DEPRECATED "These DuckDB C APIs are deprecated upstream. Prefer Database.DuckDB.FFI and its nondeprecated modules." #-} (     c_duckdb_execute_prepared_streaming, ) where 
src/Database/DuckDB/FFI/Deprecated/PendingResult.hs view
@@ -1,4 +1,5 @@-module Database.DuckDB.FFI.Deprecated.PendingResult (+module Database.DuckDB.FFI.Deprecated.PendingResult+    {-# DEPRECATED "These DuckDB C APIs are deprecated upstream. Prefer Database.DuckDB.FFI and its nondeprecated modules." #-} (     c_duckdb_pending_prepared_streaming, ) where 
src/Database/DuckDB/FFI/Deprecated/QueryExecution.hs view
@@ -1,4 +1,5 @@-module Database.DuckDB.FFI.Deprecated.QueryExecution (+module Database.DuckDB.FFI.Deprecated.QueryExecution+    {-# DEPRECATED "These DuckDB C APIs are deprecated upstream. Prefer Database.DuckDB.FFI and its nondeprecated modules." #-} (     c_duckdb_column_data,     c_duckdb_nullmask_data,     c_duckdb_row_count,
src/Database/DuckDB/FFI/Deprecated/ResultFunctions.hs view
@@ -1,4 +1,5 @@-module Database.DuckDB.FFI.Deprecated.ResultFunctions (+module Database.DuckDB.FFI.Deprecated.ResultFunctions+    {-# DEPRECATED "These DuckDB C APIs are deprecated upstream. Prefer Database.DuckDB.FFI and its nondeprecated modules." #-} (     c_duckdb_result_get_chunk,     c_duckdb_result_is_streaming,     c_duckdb_result_chunk_count,
src/Database/DuckDB/FFI/Deprecated/SafeFetch.hs view
@@ -1,4 +1,5 @@-module Database.DuckDB.FFI.Deprecated.SafeFetch (+module Database.DuckDB.FFI.Deprecated.SafeFetch+    {-# DEPRECATED "These DuckDB C APIs are deprecated upstream. Prefer Database.DuckDB.FFI and its nondeprecated modules." #-} (     c_duckdb_value_boolean,     c_duckdb_value_int8,     c_duckdb_value_int16,@@ -173,7 +174,7 @@ Returns The duckdb_date value at the specified location, or 0 if the value cannot be converted. -}-foreign import ccall safe "duckdb_value_date"+foreign import ccall safe "wrapped_duckdb_value_date"     c_duckdb_value_date :: Ptr DuckDBResult -> DuckDBIdx -> DuckDBIdx -> IO DuckDBDate  {- | > Warning Deprecation notice. This method is scheduled for removal in a future@@ -182,7 +183,7 @@ Returns The duckdb_time value at the specified location, or 0 if the value cannot be converted. -}-foreign import ccall safe "duckdb_value_time"+foreign import ccall safe "wrapped_duckdb_value_time"     c_duckdb_value_time :: Ptr DuckDBResult -> DuckDBIdx -> DuckDBIdx -> IO DuckDBTime  {- | > Warning Deprecation notice. This method is scheduled for removal in a future@@ -191,7 +192,7 @@ Returns The duckdb_timestamp value at the specified location, or 0 if the value cannot be converted. -}-foreign import ccall safe "duckdb_value_timestamp"+foreign import ccall safe "wrapped_duckdb_value_timestamp"     c_duckdb_value_timestamp :: Ptr DuckDBResult -> DuckDBIdx -> DuckDBIdx -> IO DuckDBTimestamp  {- | > Warning Deprecation notice. This method is scheduled for removal in a future
src/Database/DuckDB/FFI/Deprecated/StreamingResult.hs view
@@ -1,4 +1,5 @@-module Database.DuckDB.FFI.Deprecated.StreamingResult (+module Database.DuckDB.FFI.Deprecated.StreamingResult+    {-# DEPRECATED "These DuckDB C APIs are deprecated upstream. Prefer Database.DuckDB.FFI and its nondeprecated modules." #-} (     c_duckdb_stream_fetch_chunk, ) where 
src/Database/DuckDB/FFI/Helpers.hs view
@@ -137,7 +137,7 @@  Returns True if the date is finite, false if it is ±infinity. -}-foreign import ccall safe "duckdb_is_finite_date"+foreign import ccall safe "wrapped_duckdb_is_finite_date"     c_duckdb_is_finite_date :: DuckDBDate -> IO CBool  {- | Decompose a @duckdb_time@ object into hour, minute, second and microsecond@@ -162,7 +162,7 @@  Returns The @duckdb_time_tz@ element. -}-foreign import ccall safe "duckdb_create_time_tz"+foreign import ccall safe "wrapped_duckdb_create_time_tz"     c_duckdb_create_time_tz :: Int64 -> Int32 -> IO DuckDBTimeTz  {- | Decompose a TIME_TZ objects into micros and a timezone offset.@@ -227,7 +227,7 @@  Returns True if the timestamp is finite, false if it is ±infinity. -}-foreign import ccall safe "duckdb_is_finite_timestamp"+foreign import ccall safe "wrapped_duckdb_is_finite_timestamp"     c_duckdb_is_finite_timestamp :: DuckDBTimestamp -> IO CBool  {- | Test a @duckdb_timestamp_s@ to see if it is a finite value.@@ -238,7 +238,7 @@  Returns True if the timestamp is finite, false if it is ±infinity. -}-foreign import ccall safe "duckdb_is_finite_timestamp_s"+foreign import ccall safe "wrapped_duckdb_is_finite_timestamp_s"     c_duckdb_is_finite_timestamp_s :: DuckDBTimestampS -> IO CBool  {- | Test a @duckdb_timestamp_ms@ to see if it is a finite value.@@ -249,7 +249,7 @@  Returns True if the timestamp is finite, false if it is ±infinity. -}-foreign import ccall safe "duckdb_is_finite_timestamp_ms"+foreign import ccall safe "wrapped_duckdb_is_finite_timestamp_ms"     c_duckdb_is_finite_timestamp_ms :: DuckDBTimestampMs -> IO CBool  {- | Test a @duckdb_timestamp_ns@ to see if it is a finite value.@@ -260,7 +260,7 @@  Returns True if the timestamp is finite, false if it is ±infinity. -}-foreign import ccall safe "duckdb_is_finite_timestamp_ns"+foreign import ccall safe "wrapped_duckdb_is_finite_timestamp_ns"     c_duckdb_is_finite_timestamp_ns :: DuckDBTimestampNs -> IO CBool  {- | Converts a duckdb_hugeint object (as obtained from a @DUCKDB_TYPE_HUGEINT@
src/Database/DuckDB/FFI/Types.hs view
@@ -186,6 +186,7 @@     DuckDBBit (..),     DuckDBBignum (..),     DuckDBQueryProgress (..),+    DuckDBListEntry (..),      -- * Result Structures     DuckDBResult (..),@@ -246,6 +247,7 @@     DuckDBTaskState,     ArrowArray (..),     ArrowSchema (..),+    ArrowArrayStream (..),      -- * Opaque Struct Tags     DuckDBDatabaseStruct,@@ -737,18 +739,23 @@ -- | Invalid or unspecified file access mode. pattern DuckDBFileFlagInvalid :: DuckDBFileFlag pattern DuckDBFileFlagInvalid = DuckDBFileFlag 0+ -- | Open the file for reading. pattern DuckDBFileFlagRead :: DuckDBFileFlag pattern DuckDBFileFlagRead = DuckDBFileFlag 1+ -- | Open the file for writing. pattern DuckDBFileFlagWrite :: DuckDBFileFlag pattern DuckDBFileFlagWrite = DuckDBFileFlag 2+ -- | Create the file if it does not already exist. pattern DuckDBFileFlagCreate :: DuckDBFileFlag pattern DuckDBFileFlagCreate = DuckDBFileFlag 3+ -- | Create a new file and fail if it already exists. pattern DuckDBFileFlagCreateNew :: DuckDBFileFlag pattern DuckDBFileFlagCreateNew = DuckDBFileFlag 4+ -- | Append all writes to the end of the file. pattern DuckDBFileFlagAppend :: DuckDBFileFlag pattern DuckDBFileFlagAppend = DuckDBFileFlag 5@@ -769,12 +776,15 @@ -- | Invalid or unknown configuration scope. pattern DuckDBConfigOptionScopeInvalid :: DuckDBConfigOptionScope pattern DuckDBConfigOptionScopeInvalid = DuckDBConfigOptionScope 0--- | Scope limited to the current local operation.++-- | Scope limited to the current transaction. DuckDB does not implement this scope. pattern DuckDBConfigOptionScopeLocal :: DuckDBConfigOptionScope pattern DuckDBConfigOptionScopeLocal = DuckDBConfigOptionScope 1+ -- | Scope lasting for the current client session. pattern DuckDBConfigOptionScopeSession :: DuckDBConfigOptionScope pattern DuckDBConfigOptionScopeSession = DuckDBConfigOptionScope 2+ -- | Scope affecting the full database or process-global setting. pattern DuckDBConfigOptionScopeGlobal :: DuckDBConfigOptionScope pattern DuckDBConfigOptionScopeGlobal = DuckDBConfigOptionScope 3@@ -793,30 +803,39 @@ -- | Invalid catalog entry type. pattern DuckDBCatalogEntryTypeInvalid :: DuckDBCatalogEntryType pattern DuckDBCatalogEntryTypeInvalid = DuckDBCatalogEntryType 0+ -- | Table catalog entry. pattern DuckDBCatalogEntryTypeTable :: DuckDBCatalogEntryType pattern DuckDBCatalogEntryTypeTable = DuckDBCatalogEntryType 1+ -- | Schema catalog entry. pattern DuckDBCatalogEntryTypeSchema :: DuckDBCatalogEntryType pattern DuckDBCatalogEntryTypeSchema = DuckDBCatalogEntryType 2+ -- | View catalog entry. pattern DuckDBCatalogEntryTypeView :: DuckDBCatalogEntryType pattern DuckDBCatalogEntryTypeView = DuckDBCatalogEntryType 3+ -- | Index catalog entry. pattern DuckDBCatalogEntryTypeIndex :: DuckDBCatalogEntryType pattern DuckDBCatalogEntryTypeIndex = DuckDBCatalogEntryType 4+ -- | Prepared statement catalog entry. pattern DuckDBCatalogEntryTypePreparedStatement :: DuckDBCatalogEntryType pattern DuckDBCatalogEntryTypePreparedStatement = DuckDBCatalogEntryType 5+ -- | Sequence catalog entry. pattern DuckDBCatalogEntryTypeSequence :: DuckDBCatalogEntryType pattern DuckDBCatalogEntryTypeSequence = DuckDBCatalogEntryType 6+ -- | Collation catalog entry. pattern DuckDBCatalogEntryTypeCollation :: DuckDBCatalogEntryType pattern DuckDBCatalogEntryTypeCollation = DuckDBCatalogEntryType 7+ -- | User-defined type catalog entry. pattern DuckDBCatalogEntryTypeType :: DuckDBCatalogEntryType pattern DuckDBCatalogEntryTypeType = DuckDBCatalogEntryType 8+ -- | Attached database catalog entry. pattern DuckDBCatalogEntryTypeDatabase :: DuckDBCatalogEntryType pattern DuckDBCatalogEntryTypeDatabase = DuckDBCatalogEntryType 9@@ -834,7 +853,7 @@     , DuckDBCatalogEntryTypeDatabase     #-} --- | Represents DuckDB's @duckdb_date@.+-- | DuckDB DATE as a signed count of days since 1970-01-01. newtype DuckDBDate = DuckDBDate {unDuckDBDate :: Int32}     deriving (Eq, Ord, Show, Storable) @@ -1093,7 +1112,12 @@         pokeByteOff ptr 0 dat         pokeByteOff ptr (sizeOf (undefined :: Ptr Word8)) len --- | Represents DuckDB's @duckdb_bignum@.+{- | Represents DuckDB's @duckdb_bignum@.++@duckDBBignumData@ holds the absolute value of the number in big-endian byte+order. Free it with @duckdb_free@. The DuckDB header said little-endian until+DuckDB 1.5.4 corrected it. The byte order itself did not change.+-} data DuckDBBignum = DuckDBBignum     { duckDBBignumData :: !(Ptr Word8)     , duckDBBignumSize :: !DuckDBIdx@@ -1143,6 +1167,21 @@         pokeByteOff ptr offset1 processed         pokeByteOff ptr offset2 total +-- | Offset and length of a row in a DuckDB list vector.+data DuckDBListEntry = DuckDBListEntry+    { duckDBListEntryOffset :: !Word64+    , duckDBListEntryLength :: !Word64+    }+    deriving (Eq, Show)++instance Storable DuckDBListEntry where+    sizeOf _ = 2 * sizeOf (undefined :: Word64)+    alignment _ = alignment (undefined :: Word64)+    peek ptr = DuckDBListEntry <$> peekByteOff ptr 0 <*> peekByteOff ptr (sizeOf (undefined :: Word64))+    poke ptr (DuckDBListEntry offset len) = do+        pokeByteOff ptr 0 offset+        pokeByteOff ptr (sizeOf (undefined :: Word64)) len+ -- | Opaque DuckDB column handle. data DuckDBColumn @@ -1608,9 +1647,7 @@ type DuckDBReplacementCallback =     FunPtr (DuckDBReplacementScanInfo -> CString -> Ptr () -> IO ()) --- The full Arrow C Data Interface definitions are not included here to avoid--- introducing a dependency on the Arrow C headers. Instead, we define only the--- parts we need for testing DuckDB's Arrow integration.+-- These structures follow the Arrow C Data Interface and C Stream Interface. -- See https://arrow.apache.org/docs/format/CDataInterface.html for the full -- specification. -- #ifndef ARROW_C_DATA_INTERFACE@@ -1655,10 +1692,7 @@  -- #endif  // ARROW_C_DATA_INTERFACE -{- | Partial Arrow schema view used for tests that require inspecting DuckDB's-Arrow wrappers without depending on the full Arrow C Data Interface-definitions.--}+-- | Arrow schema structure from the Arrow C Data Interface. data ArrowSchema = ArrowSchema     { arrowSchemaFormat :: CString     , arrowSchemaName :: CString@@ -1696,7 +1730,7 @@         pokeByteOff ptr (pointerSize * 7) arrowSchemaRelease         pokeByteOff ptr (pointerSize * 8) arrowSchemaPrivateData --- | Partial Arrow array view mirroring the DuckDB C API layout.+-- | Arrow array structure from the Arrow C Data Interface. data ArrowArray = ArrowArray     { arrowArrayLength :: Int64     , arrowArrayNullCount :: Int64@@ -1737,16 +1771,40 @@         pokeByteOff ptr (intFieldSize * 5 + pointerSize * 3) arrowArrayRelease         pokeByteOff ptr (intFieldSize * 5 + pointerSize * 4) arrowArrayPrivateData -{- | Pointer wrapper for the deprecated Arrow schema handle exposed by DuckDB.-The underlying memory is managed by DuckDB and must only be accessed through-the deprecated Arrow helper functions.+-- | Arrow stream structure from the Arrow C Stream Interface.+data ArrowArrayStream = ArrowArrayStream+    { arrowStreamGetSchema :: FunPtr (Ptr ArrowArrayStream -> Ptr ArrowSchema -> IO CInt)+    , arrowStreamGetNext :: FunPtr (Ptr ArrowArrayStream -> Ptr ArrowArray -> IO CInt)+    , arrowStreamGetLastError :: FunPtr (Ptr ArrowArrayStream -> IO CString)+    , arrowStreamRelease :: FunPtr (Ptr ArrowArrayStream -> IO ())+    , arrowStreamPrivateData :: Ptr ()+    }++instance Storable ArrowArrayStream where+    sizeOf _ = pointerSize * 5+    alignment _ = alignment (nullPtr :: Ptr ())+    peek ptr =+        ArrowArrayStream+            <$> peekByteOff ptr 0+            <*> peekByteOff ptr pointerSize+            <*> peekByteOff ptr (pointerSize * 2)+            <*> peekByteOff ptr (pointerSize * 3)+            <*> peekByteOff ptr (pointerSize * 4)+    poke ptr ArrowArrayStream{..} = do+        pokeByteOff ptr 0 arrowStreamGetSchema+        pokeByteOff ptr pointerSize arrowStreamGetNext+        pokeByteOff ptr (pointerSize * 2) arrowStreamGetLastError+        pokeByteOff ptr (pointerSize * 3) arrowStreamRelease+        pokeByteOff ptr (pointerSize * 4) arrowStreamPrivateData++{- | Pointer to an Arrow schema used by the deprecated DuckDB API.+Release the schema through its release callback. The caller owns its storage. -} newtype ArrowSchemaPtr = ArrowSchemaPtr {unArrowSchemaPtr :: Ptr ArrowSchema}     deriving (Eq) -{- | Pointer wrapper for the deprecated Arrow array handle exposed by DuckDB.-DuckDB assumes exclusive ownership and coordinates buffer lifetimes via the-release callback stored in the referenced @ArrowArray@ struct.+{- | Pointer to an Arrow array used by the deprecated DuckDB API.+Release the array through its release callback. The caller owns its storage. -} newtype ArrowArrayPtr = ArrowArrayPtr {unArrowArrayPtr :: Ptr ArrowArray}     deriving (Eq)
src/Database/DuckDB/FFI/ValueInterface.hs view
@@ -280,7 +280,7 @@  Returns The value. This must be destroyed with @duckdb_destroy_value@. -}-foreign import ccall safe "duckdb_create_date"+foreign import ccall safe "wrapped_duckdb_create_date"     c_duckdb_create_date :: DuckDBDate -> IO DuckDBValue  {- | Creates a value from a time@@ -290,7 +290,7 @@  Returns The value. This must be destroyed with @duckdb_destroy_value@. -}-foreign import ccall safe "duckdb_create_time"+foreign import ccall safe "wrapped_duckdb_create_time"     c_duckdb_create_time :: DuckDBTime -> IO DuckDBValue  {- | Creates a value from a time_ns@@ -300,7 +300,7 @@  Returns The value. This must be destroyed with @duckdb_destroy_value@. -}-foreign import ccall safe "duckdb_create_time_ns"+foreign import ccall safe "wrapped_duckdb_create_time_ns"     c_duckdb_create_time_ns :: DuckDBTimeNs -> IO DuckDBValue  {- | Creates a value from a time_tz. Not to be confused with@@ -311,7 +311,7 @@  Returns The value. This must be destroyed with @duckdb_destroy_value@. -}-foreign import ccall safe "duckdb_create_time_tz_value"+foreign import ccall safe "wrapped_duckdb_create_time_tz_value"     c_duckdb_create_time_tz_value :: DuckDBTimeTz -> IO DuckDBValue  {- | Creates a TIMESTAMP value from a duckdb_timestamp@@ -321,7 +321,7 @@  Returns The value. This must be destroyed with @duckdb_destroy_value@. -}-foreign import ccall safe "duckdb_create_timestamp"+foreign import ccall safe "wrapped_duckdb_create_timestamp"     c_duckdb_create_timestamp :: DuckDBTimestamp -> IO DuckDBValue  {- | Creates a TIMESTAMP_TZ value from a duckdb_timestamp@@ -331,7 +331,7 @@  Returns The value. This must be destroyed with @duckdb_destroy_value@. -}-foreign import ccall safe "duckdb_create_timestamp_tz"+foreign import ccall safe "wrapped_duckdb_create_timestamp_tz"     c_duckdb_create_timestamp_tz :: DuckDBTimestamp -> IO DuckDBValue  {- | Creates a TIMESTAMP_S value from a duckdb_timestamp_s@@ -341,7 +341,7 @@  Returns The value. This must be destroyed with @duckdb_destroy_value@. -}-foreign import ccall safe "duckdb_create_timestamp_s"+foreign import ccall safe "wrapped_duckdb_create_timestamp_s"     c_duckdb_create_timestamp_s :: DuckDBTimestampS -> IO DuckDBValue  {- | Creates a TIMESTAMP_MS value from a duckdb_timestamp_ms@@ -351,7 +351,7 @@  Returns The value. This must be destroyed with @duckdb_destroy_value@. -}-foreign import ccall safe "duckdb_create_timestamp_ms"+foreign import ccall safe "wrapped_duckdb_create_timestamp_ms"     c_duckdb_create_timestamp_ms :: DuckDBTimestampMs -> IO DuckDBValue  {- | Creates a TIMESTAMP_NS value from a duckdb_timestamp_ns@@ -361,7 +361,7 @@  Returns The value. This must be destroyed with @duckdb_destroy_value@. -}-foreign import ccall safe "duckdb_create_timestamp_ns"+foreign import ccall safe "wrapped_duckdb_create_timestamp_ns"     c_duckdb_create_timestamp_ns :: DuckDBTimestampNs -> IO DuckDBValue  {- | Creates a value from an interval@@ -585,7 +585,7 @@  Returns A duckdb_date, or MinValue if the value cannot be converted -}-foreign import ccall safe "duckdb_get_date"+foreign import ccall safe "wrapped_duckdb_get_date"     c_duckdb_get_date :: DuckDBValue -> IO DuckDBDate  {- | Returns the time value of the given value.@@ -595,7 +595,7 @@  Returns A duckdb_time, or MinValue if the value cannot be converted -}-foreign import ccall safe "duckdb_get_time"+foreign import ccall safe "wrapped_duckdb_get_time"     c_duckdb_get_time :: DuckDBValue -> IO DuckDBTime  {- | Returns the time_ns value of the given value.@@ -605,7 +605,7 @@  Returns A duckdb_time_ns, or MinValue if the value cannot be converted -}-foreign import ccall safe "duckdb_get_time_ns"+foreign import ccall safe "wrapped_duckdb_get_time_ns"     c_duckdb_get_time_ns :: DuckDBValue -> IO DuckDBTimeNs  {- | Returns the time_tz value of the given value.@@ -615,7 +615,7 @@  Returns A duckdb_time_tz, or MinValue if the value cannot be converted -}-foreign import ccall safe "duckdb_get_time_tz"+foreign import ccall safe "wrapped_duckdb_get_time_tz"     c_duckdb_get_time_tz :: DuckDBValue -> IO DuckDBTimeTz  {- | Returns the TIMESTAMP value of the given value.@@ -625,7 +625,7 @@  Returns A duckdb_timestamp, or MinValue if the value cannot be converted -}-foreign import ccall safe "duckdb_get_timestamp"+foreign import ccall safe "wrapped_duckdb_get_timestamp"     c_duckdb_get_timestamp :: DuckDBValue -> IO DuckDBTimestamp  {- | Returns the TIMESTAMP_TZ value of the given value.@@ -635,7 +635,7 @@  Returns A duckdb_timestamp, or MinValue if the value cannot be converted -}-foreign import ccall safe "duckdb_get_timestamp_tz"+foreign import ccall safe "wrapped_duckdb_get_timestamp_tz"     c_duckdb_get_timestamp_tz :: DuckDBValue -> IO DuckDBTimestamp  {- | Returns the duckdb_timestamp_s value of the given value.@@ -645,7 +645,7 @@  Returns A duckdb_timestamp_s, or MinValue if the value cannot be converted -}-foreign import ccall safe "duckdb_get_timestamp_s"+foreign import ccall safe "wrapped_duckdb_get_timestamp_s"     c_duckdb_get_timestamp_s :: DuckDBValue -> IO DuckDBTimestampS  {- | Returns the duckdb_timestamp_ms value of the given value.@@ -655,7 +655,7 @@  Returns A duckdb_timestamp_ms, or MinValue if the value cannot be converted -}-foreign import ccall safe "duckdb_get_timestamp_ms"+foreign import ccall safe "wrapped_duckdb_get_timestamp_ms"     c_duckdb_get_timestamp_ms :: DuckDBValue -> IO DuckDBTimestampMs  {- | Returns the duckdb_timestamp_ns value of the given value.@@ -665,7 +665,7 @@  Returns A duckdb_timestamp_ns, or MinValue if the value cannot be converted -}-foreign import ccall safe "duckdb_get_timestamp_ns"+foreign import ccall safe "wrapped_duckdb_get_timestamp_ns"     c_duckdb_get_timestamp_ns :: DuckDBValue -> IO DuckDBTimestampNs  {- | Returns the interval value of the given value.
test/AggregateFunctionsTest.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE BlockArguments #-} {-# LANGUAGE ForeignFunctionInterface #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module AggregateFunctionsTest (tests) where 
test/AppenderTest.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE BlockArguments #-} {-# LANGUAGE TypeApplications #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module AppenderTest (tests) where 
test/ArrowInterfaceDeprecatedTests.hs view
@@ -1,10 +1,11 @@ {-# LANGUAGE BlockArguments #-} {-# LANGUAGE ForeignFunctionInterface #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module ArrowInterfaceDeprecatedTests (tests) where  import Control.Exception (bracket, finally)-import Control.Monad (unless, when)+import Control.Monad (unless, when, (>=>)) import Data.Int (Int32, Int64) import Data.List (isInfixOf) import Data.Maybe (isNothing)@@ -13,7 +14,7 @@ import Foreign.C.String (peekCString, peekCStringLen, withCString) import Foreign.C.Types (CChar) import Foreign.Marshal.Alloc (alloca)-import Foreign.Ptr (FunPtr, Ptr, castPtr, nullFunPtr, nullPtr, plusPtr)+import Foreign.Ptr (Ptr, castPtr, nullFunPtr, nullPtr, plusPtr) import Foreign.Storable (peek, peekElemOff, poke) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (assertBool, assertFailure, testCase, (@?=))@@ -85,24 +86,21 @@  preparedArrowSchemaMatchesStatement :: TestTree preparedArrowSchemaMatchesStatement =-    testCase "prepared_arrow_schema reflects projected columns" $+    testCase "prepared_arrow_schema describes statement parameters" $         withDatabase \db ->             withConnection db \conn ->-                withPrepared conn "SELECT id, label FROM (VALUES (1, 'a')) AS t(id, label)" \stmt -> do-                    alloca \schemaOut -> do-                        poke schemaOut nullPtr-                        stSchema <- c_duckdb_prepared_arrow_schema stmt schemaOut-                        case stSchema of-                            DuckDBSuccess -> do-                                schemaWrapper <- peek schemaOut-                                assertBool "prepared arrow schema pointer should not be null" (schemaWrapper /= nullPtr)-                            DuckDBError -> do-                                schemaWrapper <- peek schemaOut-                                schemaWrapper @?= nullPtr-                                errPtr <- c_duckdb_prepare_error stmt-                                when (errPtr /= nullPtr) $ do-                                    errMsg <- peekCString errPtr-                                    errMsg @?= ""+                withPrepared conn "SELECT ?::INTEGER, ?::VARCHAR" \stmt ->+                    alloca \schemaStorage -> do+                        poke schemaStorage zeroArrowSchema+                        alloca \schemaOut -> do+                            poke schemaOut (castPtr schemaStorage)+                            c_duckdb_prepared_arrow_schema stmt schemaOut >>= (@?= DuckDBSuccess)+                            schema <- peek schemaStorage+                            arrowSchemaChildCount schema @?= 2+                            children <- mapM (peekElemOff (arrowSchemaChildren schema) >=> peek) [0, 1]+                            mapM (peekCString . arrowSchemaName) children >>= (@?= ["0", "1"])+                            mapM (peekCString . arrowSchemaFormat) children >>= (@?= ["n", "n"])+                            releaseArrowSchema schemaStorage  executePreparedArrowProducesRows :: TestTree executePreparedArrowProducesRows =@@ -281,47 +279,99 @@  arrowPointerHelpersClearInternalState :: TestTree arrowPointerHelpersClearInternalState =-    -- NOTE: The deprecated Arrow handles are thin wrappers whose-    -- internal_ptr fields can be inspected/reset via the exported-    -- duckdbArrow{Schema,Array,Stream}{Internal,Clear} helpers. We avoid-    -- direct foreign imports here so future changes to the shims only-    -- require updates in the Arrow module.-    testCase "arrow pointer helper functions read and clear internal_ptr fields" $-        alloca \schemaFieldPtr ->-            alloca \arrayFieldPtr ->-                alloca \streamFieldPtr -> do-                    let schemaField = schemaFieldPtr :: Ptr (Ptr ())-                        schemaHandle = castPtr schemaField :: DuckDBArrowSchema-                        schemaSentinel = ArrowSchemaPtr (castPtr schemaField)-                    poke schemaField (castPtr (unArrowSchemaPtr schemaSentinel))-                    schemaPtr <- duckdbArrowSchemaInternal schemaHandle-                    fmap unArrowSchemaPtr schemaPtr @?= Just (unArrowSchemaPtr schemaSentinel)+    testCase "Arrow helpers move real DuckDB structures without releasing buffers" $ do+        withDatabase \db ->+            withConnection db \conn ->+                withSuccessfulArrow conn "SELECT 42::BIGINT AS id, 'duck' AS label" \arrow ->+                    alloca \schemaStorage -> do+                        poke schemaStorage zeroArrowSchema+                        let schemaHandle = castPtr schemaStorage :: DuckDBArrowSchema+                        alloca \schemaOut -> do+                            poke schemaOut schemaHandle+                            c_duckdb_query_arrow_schema arrow schemaOut >>= (@?= DuckDBSuccess)+                        schemaPtr <- duckdbArrowSchemaInternal schemaHandle+                        fmap unArrowSchemaPtr schemaPtr @?= Just schemaStorage -                    duckdbArrowSchemaClear schemaHandle-                    schemaCleared <- duckdbArrowSchemaInternal schemaHandle-                    assertBool "schema internal_ptr should be cleared" (isNothing schemaCleared)+                        alloca \arrayStorage -> do+                            poke arrayStorage zeroArrowArray+                            let arrayHandle = castPtr arrayStorage :: DuckDBArrowArray+                            alloca \arrayOut -> do+                                poke arrayOut arrayHandle+                                c_duckdb_query_arrow_array arrow arrayOut >>= (@?= DuckDBSuccess)+                            arrayPtr <- duckdbArrowArrayInternal arrayHandle+                            fmap unArrowArrayPtr arrayPtr @?= Just arrayStorage -                    let arrayField = arrayFieldPtr :: Ptr (Ptr ())-                        arrayHandle = castPtr arrayField :: DuckDBArrowArray-                        arraySentinel = ArrowArrayPtr (castPtr arrayField)-                    poke arrayField (castPtr (unArrowArrayPtr arraySentinel))-                    arrayPtr <- duckdbArrowArrayInternal arrayHandle-                    fmap unArrowArrayPtr arrayPtr @?= Just (unArrowArrayPtr arraySentinel)+                            withCString "arrow_helper_view" \viewName ->+                                alloca \streamOut -> do+                                    poke streamOut nullPtr+                                    c_duckdb_arrow_array_scan conn viewName schemaHandle arrayHandle streamOut >>= (@?= DuckDBSuccess)+                                    stream <- peek streamOut+                                    streamPtr <- duckdbArrowStreamInternal stream+                                    fmap unArrowStreamPtr streamPtr @?= Just (castPtr stream)+                                    original <- peek (castPtr stream :: Ptr ArrowArrayStream)+                                    alloca \movedStream -> do+                                        poke movedStream original+                                        duckdbArrowStreamClear stream+                                        source <- peek (castPtr stream :: Ptr ArrowArrayStream)+                                        arrowStreamRelease source @?= nullFunPtr+                                        arrowStreamGetSchema source @?= arrowStreamGetSchema original+                                        duckdbArrowStreamInternal stream >>= assertBool "detached stream" . isNothing+                                        alloca \movedSchema -> do+                                            poke movedSchema zeroArrowSchema+                                            mkArrowStreamGetSchema (arrowStreamGetSchema original) movedStream movedSchema >>= (@?= 0)+                                            schema <- peek movedSchema+                                            peekCString (arrowSchemaFormat schema) >>= (@?= "+s")+                                            releaseArrowSchema movedSchema+                                        alloca \movedArray -> do+                                            poke movedArray zeroArrowArray+                                            mkArrowStreamGetNext (arrowStreamGetNext original) movedStream movedArray >>= (@?= 0)+                                            batch <- peek movedArray+                                            arrowArrayLength batch @?= 1+                                            releaseArrowArray movedArray+                                            mkArrowStreamGetNext (arrowStreamGetNext original) movedStream movedArray >>= (@?= 0)+                                            exhausted <- peek movedArray+                                            arrowArrayRelease exhausted @?= nullFunPtr+                                        mkArrowStreamRelease (arrowStreamRelease original) movedStream+                                        moved <- peek movedStream+                                        arrowStreamRelease moved @?= nullFunPtr+                                    c_duckdb_destroy_arrow_stream streamOut+                                    peek streamOut >>= (@?= nullPtr) -                    duckdbArrowArrayClear arrayHandle-                    arrayCleared <- duckdbArrowArrayInternal arrayHandle-                    assertBool "array internal_ptr should be cleared" (isNothing arrayCleared)+                            originalArray <- peek arrayStorage+                            alloca \movedArray -> do+                                poke movedArray originalArray+                                duckdbArrowArrayClear arrayHandle+                                source <- peek arrayStorage+                                arrowArrayLength source @?= arrowArrayLength originalArray+                                arrowArrayBuffers source @?= arrowArrayBuffers originalArray+                                arrowArrayRelease source @?= nullFunPtr+                                duckdbArrowArrayInternal arrayHandle >>= assertBool "detached array" . isNothing+                                moved <- peek movedArray+                                arrowArrayLength moved @?= 1+                                child <- peekElemOff (arrowArrayChildren moved) 0 >>= peek+                                buffer <- peekElemOff (arrowArrayBuffers child) 1+                                peek (castPtr buffer :: Ptr Int64) >>= (@?= 42)+                                releaseArrowArray movedArray+                                duckdbArrowArrayClear arrayHandle -                    let streamField = streamFieldPtr :: Ptr (Ptr ())-                        streamHandle = castPtr streamField :: DuckDBArrowStream-                        streamSentinel = ArrowStreamPtr (castPtr streamField)-                    poke streamField (castPtr (unArrowStreamPtr streamSentinel))-                    streamPtr <- duckdbArrowStreamInternal streamHandle-                    fmap unArrowStreamPtr streamPtr @?= Just (unArrowStreamPtr streamSentinel)+                        originalSchema <- peek schemaStorage+                        alloca \movedSchema -> do+                            poke movedSchema originalSchema+                            duckdbArrowSchemaClear schemaHandle+                            source <- peek schemaStorage+                            arrowSchemaFormat source @?= arrowSchemaFormat originalSchema+                            arrowSchemaRelease source @?= nullFunPtr+                            duckdbArrowSchemaInternal schemaHandle >>= assertBool "detached schema" . isNothing+                            moved <- peek movedSchema+                            peekCString (arrowSchemaFormat moved) >>= (@?= "+s")+                            child <- peekElemOff (arrowSchemaChildren moved) 0 >>= peek+                            peekCString (arrowSchemaName child) >>= (@?= "id")+                            releaseArrowSchema movedSchema+                            duckdbArrowSchemaClear schemaHandle -                    duckdbArrowStreamClear streamHandle-                    streamCleared <- duckdbArrowStreamInternal streamHandle-                    assertBool "stream internal_ptr should be cleared" (isNothing streamCleared)+        duckdbArrowSchemaInternal nullPtr >>= assertBool "null schema" . isNothing+        duckdbArrowArrayInternal nullPtr >>= assertBool "null array" . isNothing+        duckdbArrowStreamInternal nullPtr >>= assertBool "null stream" . isNothing  zeroArrowArray :: ArrowArray zeroArrowArray =@@ -408,28 +458,6 @@             )             [0 .. rowCount - 1]     labels @?= expectedLabels--releaseArrowArray :: Ptr ArrowArray -> IO ()-releaseArrowArray arrayPtr = do-    array <- peek arrayPtr-    let releaseFun = arrowArrayRelease array-    when (releaseFun /= nullFunPtr) $ do-        let release = mkArrowArrayRelease releaseFun-        release arrayPtr--releaseArrowSchema :: Ptr ArrowSchema -> IO ()-releaseArrowSchema schemaPtr = do-    schema <- peek schemaPtr-    let releaseFun = arrowSchemaRelease schema-    when (releaseFun /= nullFunPtr) $ do-        let release = mkArrowSchemaRelease releaseFun-        release schemaPtr--foreign import ccall "dynamic"-    mkArrowArrayRelease :: FunPtr (Ptr ArrowArray -> IO ()) -> Ptr ArrowArray -> IO ()--foreign import ccall "dynamic"-    mkArrowSchemaRelease :: FunPtr (Ptr ArrowSchema -> IO ()) -> Ptr ArrowSchema -> IO ()  withSuccessfulArrow :: DuckDBConnection -> String -> (DuckDBArrow -> IO a) -> IO a withSuccessfulArrow conn sql action =
test/ArrowInterfaceTest.hs view
@@ -2,7 +2,7 @@  module ArrowInterfaceTest (tests) where -import Control.Exception (bracket)+import Control.Exception (bracket, finally) import Control.Monad (when) import Data.Bits (testBit) import Data.Int (Int32)@@ -11,7 +11,7 @@ import Foreign.Marshal.Alloc (alloca) import Foreign.Marshal.Array (peekArray, withArray) import Foreign.Marshal.Utils (withMany)-import Foreign.Ptr (FunPtr, Ptr, castPtr, nullFunPtr, nullPtr)+import Foreign.Ptr (Ptr, castPtr, freeHaskellFunPtr, nullFunPtr, nullPtr) import Foreign.Storable (Storable (..), peek, peekElemOff, poke, pokeElemOff) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (assertBool, assertFailure, testCase, (@?=))@@ -23,8 +23,34 @@         "Arrow Interface"         [ arrowSchemaRoundtrip         , arrowChunkRoundtrip+        , arrowStreamErrorCallbacks         ] +-- | Invoke a producer's error callback after its next-array callback fails.+arrowStreamErrorCallbacks :: TestTree+arrowStreamErrorCallbacks =+    testCase "stream callbacks report a producer error" $+        withCString "Arrow producer failed" \message ->+            bracket (wrapArrowStreamGetNext (\_ _ -> pure 5)) freeHaskellFunPtr \next ->+                bracket (wrapArrowStreamGetLastError (const (pure message))) freeHaskellFunPtr \lastError ->+                    bracket+                        ( wrapArrowStreamRelease \stream -> do+                            value <- peek stream+                            poke stream value{arrowStreamRelease = nullFunPtr}+                        )+                        freeHaskellFunPtr+                        \release ->+                            alloca \stream -> do+                                poke stream (ArrowArrayStream nullFunPtr next lastError release nullPtr)+                                alloca \array -> do+                                    poke array zeroArrowArray+                                    mkArrowStreamGetNext next stream array >>= (@?= 5)+                                    mkArrowStreamGetLastError lastError stream >>= peekCString >>= (@?= "Arrow producer failed")+                                releaseArrowStream stream+                                value <- peek stream+                                arrowStreamRelease value @?= nullFunPtr+                                releaseArrowStream stream+ arrowSchemaRoundtrip :: TestTree arrowSchemaRoundtrip =     testCase "to_arrow_schema exposes children and converts back" $@@ -53,8 +79,6 @@                                      withConvertedSchema conn schemaPtr (const (pure ())) -                                    releaseArrowSchema schemaPtr- arrowChunkRoundtrip :: TestTree arrowChunkRoundtrip =     testCase "data_chunk_to_arrow and from_arrow preserve values and nulls" $@@ -122,8 +146,6 @@                                                             arrayAfter <- peek arrayPtr                                                             arrowArrayRelease arrayAfter @?= nullFunPtr -                                    releaseArrowSchema schemaPtr- withArrowOptions :: DuckDBConnection -> (DuckDBArrowOptions -> IO a) -> IO a withArrowOptions conn action =     alloca \optsPtr -> do@@ -147,14 +169,12 @@     withMany withCString names $ \cNames -> withArray cNames action  withArrowSchema :: (Ptr ArrowSchema -> IO a) -> IO a-withArrowSchema = withStruct zeroArrowSchema+withArrowSchema action =+    withStruct zeroArrowSchema \ptr -> action ptr `finally` releaseArrowSchema ptr  withArrowArray :: (Ptr ArrowArray -> IO a) -> IO a withArrowArray action =-    withStruct zeroArrowArray \ptr -> do-        result <- action ptr-        releaseArrowArray ptr-        pure result+    withStruct zeroArrowArray \ptr -> action ptr `finally` releaseArrowArray ptr  withStruct :: (Storable a) => a -> (Ptr a -> IO b) -> IO b withStruct initial action =@@ -207,22 +227,6 @@         destroyErrorData err         assertFailure ("DuckDB reported error: " <> msg) -releaseArrowSchema :: Ptr ArrowSchema -> IO ()-releaseArrowSchema schemaPtr = do-    schema <- peek schemaPtr-    let releaseFun = arrowSchemaRelease schema-    when (releaseFun /= nullFunPtr) $ do-        let release = mkArrowSchemaRelease releaseFun-        release schemaPtr--releaseArrowArray :: Ptr ArrowArray -> IO ()-releaseArrowArray arrayPtr = do-    array <- peek arrayPtr-    let releaseFun = arrowArrayRelease array-    when (releaseFun /= nullFunPtr) $ do-        let release = mkArrowArrayRelease releaseFun-        release arrayPtr- zeroArrowSchema :: ArrowSchema zeroArrowSchema =     ArrowSchema@@ -251,9 +255,3 @@         , arrowArrayRelease = nullFunPtr         , arrowArrayPrivateData = nullPtr         }--foreign import ccall "dynamic"-    mkArrowSchemaRelease :: FunPtr (Ptr ArrowSchema -> IO ()) -> Ptr ArrowSchema -> IO ()--foreign import ccall "dynamic"-    mkArrowArrayRelease :: FunPtr (Ptr ArrowArray -> IO ()) -> Ptr ArrowArray -> IO ()
test/BindValuesTest.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE BlockArguments #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module BindValuesTest (tests) where @@ -23,13 +24,25 @@ tests =     testGroup         "Bind Values"-        [bindValuesRoundtrip]+        [bindValuesRoundtrip, bindTimestampTzAcrossSessions] +{- | Pin the session timezone so the tstz readback offset below is stable+regardless of the host OS timezone (see issue #8).+-}+setSessionTimeZoneSQL :: String+setSessionTimeZoneSQL = "SET TimeZone='UTC+02:00'"+ bindValuesRoundtrip :: TestTree bindValuesRoundtrip =     testCase "bind every supported value type" $         withDatabase \db ->             withConnection db \conn -> do+                withCString setSessionTimeZoneSQL \tzSQL ->+                    alloca \resPtr -> do+                        st <- c_duckdb_query conn tzSQL resPtr+                        st @?= DuckDBSuccess+                        c_duckdb_destroy_result resPtr+                 withCString createSQL \ddl ->                     alloca \resPtr -> do                         st <- c_duckdb_query conn ddl resPtr@@ -255,7 +268,7 @@             <> "date_col DATE,"             <> "time_col TIME,"             <> "ts_col TIMESTAMP,"-            <> "tstz_col TIMESTAMP,"+            <> "tstz_col TIMESTAMPTZ,"             <> "interval_col INTERVAL,"             <> "decimal_col DECIMAL(18,2),"             <> "varchar_col VARCHAR,"@@ -269,8 +282,73 @@             <> intercalate ", " (replicate 24 "?")             <> ")" -    selectSQL = "SELECT * FROM bind_values"+    -- Cast tstz_col back to TIMESTAMP so the deprecated safe-fetch+    -- 'c_duckdb_value_timestamp' can read it; the cast uses the pinned+    -- session TimeZone above, yielding bound + 02:00.+    selectSQL = "SELECT * REPLACE (tstz_col::TIMESTAMP AS tstz_col) FROM bind_values"      duckDBDateDays (DuckDBDate d) = d     duckDBTimeMicros (DuckDBTime t) = t     duckDBTimestampMicros (DuckDBTimestamp t) = t++{- | Regression test for issue #8: reading a TIMESTAMPTZ column via+'c_duckdb_value_timestamp' converts to the session's TimeZone, so the+offset between the bound UTC instant and the readback must track the+session's TimeZone setting — not the host OS timezone.+-}+bindTimestampTzAcrossSessions :: TestTree+bindTimestampTzAcrossSessions =+    testCase "bind_timestamp_tz readback tracks session TimeZone" $+        withDatabase \db ->+            withConnection db \conn -> do+                withCString "CREATE TABLE tstz_only (ts TIMESTAMPTZ)" \ddl ->+                    alloca \resPtr -> do+                        c_duckdb_query conn ddl resPtr >>= (@?= DuckDBSuccess)+                        c_duckdb_destroy_result resPtr++                -- A fixed UTC instant: 2021-07-20 12:34:56 UTC+                let boundMicros :: Int64+                    boundMicros =+                        fromIntegral (diffDays (fromGregorian 2021 7 20) (fromGregorian 1970 1 1))+                            * 86400000000+                            + ((12 * 60 + 34) * 60 + 56) * 1000000+                    bound = DuckDBTimestamp boundMicros++                withCString "INSERT INTO tstz_only VALUES (?)" \cInsert ->+                    alloca \stmtPtr -> do+                        c_duckdb_prepare conn cInsert stmtPtr >>= (@?= DuckDBSuccess)+                        stmt <- peek stmtPtr+                        c_duckdb_bind_timestamp_tz stmt 1 bound >>= (@?= DuckDBSuccess)+                        alloca \execResPtr -> do+                            c_duckdb_execute_prepared stmt execResPtr >>= (@?= DuckDBSuccess)+                            c_duckdb_destroy_result execResPtr+                        c_duckdb_destroy_prepare stmtPtr++                -- For each session TimeZone, the readback wall-clock should be+                -- bound + offset, independent of the host OS TZ.+                let cases :: [(String, Int64)]+                    cases =+                        [ ("UTC", 0)+                        , ("UTC+02:00", 2 * 3600 * 1000000)+                        , ("UTC-05:00", (-5) * 3600 * 1000000)+                        , ("Etc/GMT-2", 2 * 3600 * 1000000)+                        ]++                mapM_ (assertTimeZoneOffset conn boundMicros) cases+  where+    assertTimeZoneOffset conn boundMicros (tz, expected) = do+        withCString ("SET TimeZone='" <> tz <> "'") \setTzSQL ->+            alloca \resPtr -> do+                c_duckdb_query conn setTzSQL resPtr >>= (@?= DuckDBSuccess)+                c_duckdb_destroy_result resPtr++        -- Cast TIMESTAMPTZ → TIMESTAMP in-query; the cast uses the+        -- session TimeZone just set, so the wall-clock readback carries+        -- the expected offset.+        withCString "SELECT ts::TIMESTAMP FROM tstz_only" \cSelect ->+            alloca \resPtr -> do+                c_duckdb_query conn cSelect resPtr >>= (@?= DuckDBSuccess)+                DuckDBTimestamp fetched <- c_duckdb_value_timestamp resPtr 0 0+                let msg = "TimeZone=" <> tz <> ": expected offset " <> show expected+                assertBool msg (fetched - boundMicros == expected)+                c_duckdb_destroy_result resPtr
test/CastFunctionsTest.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE BlockArguments #-} {-# LANGUAGE ForeignFunctionInterface #-} {-# LANGUAGE NamedFieldPuns #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module CastFunctionsTest (tests) where 
test/ExecutePreparedStatementsTest.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE BlockArguments #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module ExecutePreparedStatementsTest (tests) where 
test/ExtractStatementsTest.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE BlockArguments #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module ExtractStatementsTest (tests) where 
test/LogicalTypesTest.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE BlockArguments #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module LogicalTypesTest (tests) where 
test/OpenConnectTest.hs view
@@ -3,15 +3,18 @@  module OpenConnectTest (tests) where -import Control.Monad (when)+import Control.Monad (forM_, when)+import Data.Version (Version (..), parseVersion) import Database.DuckDB.FFI import Foreign.C.String (peekCString, withCString) import Foreign.C.Types (CBool (..)) import Foreign.Marshal.Alloc (alloca) import Foreign.Ptr (castPtr, nullPtr) import Foreign.Storable (peek, poke)+import System.Environment (lookupEnv) import Test.Tasty (TestTree, testGroup)-import Test.Tasty.HUnit (assertBool, testCase, (@?=))+import Test.Tasty.HUnit (assertBool, assertFailure, testCase, (@?=))+import Text.ParserCombinators.ReadP (char, eof, readP_to_S)  tests :: TestTree tests =@@ -25,10 +28,15 @@  testLibraryVersion :: TestTree testLibraryVersion =-    testCase "duckdb_library_version returns a non-empty string" $ do+    testCase "loads a supported DuckDB runtime" $ do         versionPtr <- c_duckdb_library_version         version <- peekCString versionPtr-        assertBool "library version should not be empty" (not (null version))+        let unsupported = "Expected DuckDB >= 1.5.3 and < 1.6; loaded " <> version+        case readP_to_S (char 'v' *> parseVersion <* eof) version of+            [(Version [1, 5, patch] [], "")] -> assertBool unsupported (patch >= 3)+            _ -> assertFailure unsupported+        expected <- lookupEnv "DUCKDB_TEST_VERSION"+        forM_ expected $ \wanted -> version @?= ('v' : wanted)  testOpenAndConnect :: TestTree testOpenAndConnect =
test/PendingResultTest.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE BlockArguments #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module PendingResultTest (tests) where 
test/QueryExecutionTest.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE BlockArguments #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module QueryExecutionTest (tests) where 
test/ReplacementScansTest.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE BlockArguments #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module ReplacementScansTest (tests) where 
test/ResultFunctionsTest.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE BlockArguments #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module ResultFunctionsTest (tests) where 
test/SafeFetchTest.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE BlockArguments #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module SafeFetchTest (tests) where 
test/ScalarFunctionsTest.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE BlockArguments #-} {-# LANGUAGE ForeignFunctionInterface #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module ScalarFunctionsTest (tests) where 
test/StreamingResultTest.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE BlockArguments #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module StreamingResultTest (tests) where 
test/TableFunctionsTest.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE BlockArguments #-} {-# LANGUAGE ForeignFunctionInterface #-} {-# LANGUAGE RecordWildCards #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module TableFunctionsTest (tests) where 
test/ThreadingTest.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE BlockArguments #-}+{-# OPTIONS_GHC -Wno-deprecations #-}  module ThreadingTest (tests) where @@ -13,7 +14,7 @@ import Foreign.Storable (peek) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (assertBool, assertFailure, testCase, (@?=))-import Utils (withConnection, withDatabase)+import Utils (withConnection)  tests :: TestTree tests =@@ -23,10 +24,33 @@         , taskStateControlsExecutionLifecycle         ] +{- | Open a database that has no worker threads. Without this, the+worker threads of DuckDB start a pending query immediately, and the+test races them. With one thread, only 'c_duckdb_execute_tasks' can+make progress, so the checks are deterministic.+-}+withSingleThreadedDatabase :: (DuckDBDatabase -> IO a) -> IO a+withSingleThreadedDatabase action =+    withCString ":memory:" \path ->+        alloca \configPtr -> do+            c_duckdb_create_config configPtr >>= (@?= DuckDBSuccess)+            config <- peek configPtr+            withCString "threads" \flag ->+                withCString "1" \value ->+                    c_duckdb_set_config config flag value >>= (@?= DuckDBSuccess)+            alloca \dbPtr -> do+                st <- c_duckdb_open_ext path dbPtr config nullPtr+                c_duckdb_destroy_config configPtr+                st @?= DuckDBSuccess+                db <- peek dbPtr+                result <- action db+                c_duckdb_close dbPtr+                pure result+ executeTasksCompletesPendingQuery :: TestTree executeTasksCompletesPendingQuery =     testCase "execute_tasks drives pending query to completion" $-        withDatabase \db ->+        withSingleThreadedDatabase \db ->             withConnection db \conn -> do                 setupAggTable conn @@ -65,7 +89,7 @@ taskStateControlsExecutionLifecycle :: TestTree taskStateControlsExecutionLifecycle =     testCase "task state executes batches and finishes on request" $-        withDatabase \db ->+        withSingleThreadedDatabase \db ->             withConnection db \conn -> do                 setupAggTable conn 
test/VectorTest.hs view
@@ -81,12 +81,10 @@                      childVec <- c_duckdb_list_vector_get_child listVec                     metaRaw <- c_duckdb_vector_get_data listVec-                    let metaWords = castPtr metaRaw :: Ptr Word64+                    let metaEntries = castPtr metaRaw :: Ptr DuckDBListEntry                     -- row 0 -> offset 0 length 3, row 1 -> offset 3 length 2-                    pokeElemOff metaWords 0 0-                    pokeElemOff metaWords 1 3-                    pokeElemOff metaWords 2 3-                    pokeElemOff metaWords 3 2+                    pokeElemOff metaEntries 0 (DuckDBListEntry 0 3)+                    pokeElemOff metaEntries 1 (DuckDBListEntry 3 2)                      childRaw <- c_duckdb_vector_get_data childVec                     let childInts = castPtr childRaw :: Ptr Int32@@ -95,8 +93,8 @@                     fetched <- mapM (peekElemOff childInts) [0 .. length payload - 1]                     fetched @?= payload -                    peekElemOff metaWords 1 >>= (@?= 3)-                    peekElemOff metaWords 3 >>= (@?= 2)+                    peekElemOff metaEntries 0 >>= (@?= DuckDBListEntry 0 3)+                    peekElemOff metaEntries 1 >>= (@?= DuckDBListEntry 3 2)  -- | Verify array child vector exposes a flat buffer sized by row * array length. arrayVectorChildAccess :: TestTree