diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.
diff --git a/Setup.hs b/Setup.hs
--- a/Setup.hs
+++ b/Setup.hs
@@ -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
diff --git a/cbits/duckdb_arrow.h b/cbits/duckdb_arrow.h
new file mode 100644
--- /dev/null
+++ b/cbits/duckdb_arrow.h
@@ -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
diff --git a/cbits/duckdb_stub.c b/cbits/duckdb_stub.c
--- a/cbits/duckdb_stub.c
+++ b/cbits/duckdb_stub.c
@@ -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;
 }
diff --git a/duckdb-ffi.cabal b/duckdb-ffi.cabal
--- a/duckdb-ffi.cabal
+++ b/duckdb-ffi.cabal
@@ -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
diff --git a/src/Database/DuckDB/FFI/Appender.hs b/src/Database/DuckDB/FFI/Appender.hs
--- a/src/Database/DuckDB/FFI/Appender.hs
+++ b/src/Database/DuckDB/FFI/Appender.hs
@@ -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.
diff --git a/src/Database/DuckDB/FFI/Arrow.hs b/src/Database/DuckDB/FFI/Arrow.hs
--- a/src/Database/DuckDB/FFI/Arrow.hs
+++ b/src/Database/DuckDB/FFI/Arrow.hs
@@ -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
diff --git a/src/Database/DuckDB/FFI/BindValues.hs b/src/Database/DuckDB/FFI/BindValues.hs
--- a/src/Database/DuckDB/FFI/BindValues.hs
+++ b/src/Database/DuckDB/FFI/BindValues.hs
@@ -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
diff --git a/src/Database/DuckDB/FFI/Deprecated.hs b/src/Database/DuckDB/FFI/Deprecated.hs
--- a/src/Database/DuckDB/FFI/Deprecated.hs
+++ b/src/Database/DuckDB/FFI/Deprecated.hs
@@ -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,
diff --git a/src/Database/DuckDB/FFI/Deprecated/Appender.hs b/src/Database/DuckDB/FFI/Deprecated/Appender.hs
--- a/src/Database/DuckDB/FFI/Deprecated/Appender.hs
+++ b/src/Database/DuckDB/FFI/Deprecated/Appender.hs
@@ -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
 
diff --git a/src/Database/DuckDB/FFI/Deprecated/Arrow.hs b/src/Database/DuckDB/FFI/Deprecated/Arrow.hs
--- a/src/Database/DuckDB/FFI/Deprecated/Arrow.hs
+++ b/src/Database/DuckDB/FFI/Deprecated/Arrow.hs
@@ -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
diff --git a/src/Database/DuckDB/FFI/Deprecated/ExecutePrepared.hs b/src/Database/DuckDB/FFI/Deprecated/ExecutePrepared.hs
--- a/src/Database/DuckDB/FFI/Deprecated/ExecutePrepared.hs
+++ b/src/Database/DuckDB/FFI/Deprecated/ExecutePrepared.hs
@@ -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
 
diff --git a/src/Database/DuckDB/FFI/Deprecated/PendingResult.hs b/src/Database/DuckDB/FFI/Deprecated/PendingResult.hs
--- a/src/Database/DuckDB/FFI/Deprecated/PendingResult.hs
+++ b/src/Database/DuckDB/FFI/Deprecated/PendingResult.hs
@@ -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
 
diff --git a/src/Database/DuckDB/FFI/Deprecated/QueryExecution.hs b/src/Database/DuckDB/FFI/Deprecated/QueryExecution.hs
--- a/src/Database/DuckDB/FFI/Deprecated/QueryExecution.hs
+++ b/src/Database/DuckDB/FFI/Deprecated/QueryExecution.hs
@@ -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,
diff --git a/src/Database/DuckDB/FFI/Deprecated/ResultFunctions.hs b/src/Database/DuckDB/FFI/Deprecated/ResultFunctions.hs
--- a/src/Database/DuckDB/FFI/Deprecated/ResultFunctions.hs
+++ b/src/Database/DuckDB/FFI/Deprecated/ResultFunctions.hs
@@ -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,
diff --git a/src/Database/DuckDB/FFI/Deprecated/SafeFetch.hs b/src/Database/DuckDB/FFI/Deprecated/SafeFetch.hs
--- a/src/Database/DuckDB/FFI/Deprecated/SafeFetch.hs
+++ b/src/Database/DuckDB/FFI/Deprecated/SafeFetch.hs
@@ -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
diff --git a/src/Database/DuckDB/FFI/Deprecated/StreamingResult.hs b/src/Database/DuckDB/FFI/Deprecated/StreamingResult.hs
--- a/src/Database/DuckDB/FFI/Deprecated/StreamingResult.hs
+++ b/src/Database/DuckDB/FFI/Deprecated/StreamingResult.hs
@@ -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
 
diff --git a/src/Database/DuckDB/FFI/Helpers.hs b/src/Database/DuckDB/FFI/Helpers.hs
--- a/src/Database/DuckDB/FFI/Helpers.hs
+++ b/src/Database/DuckDB/FFI/Helpers.hs
@@ -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@
diff --git a/src/Database/DuckDB/FFI/Types.hs b/src/Database/DuckDB/FFI/Types.hs
--- a/src/Database/DuckDB/FFI/Types.hs
+++ b/src/Database/DuckDB/FFI/Types.hs
@@ -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)
diff --git a/src/Database/DuckDB/FFI/ValueInterface.hs b/src/Database/DuckDB/FFI/ValueInterface.hs
--- a/src/Database/DuckDB/FFI/ValueInterface.hs
+++ b/src/Database/DuckDB/FFI/ValueInterface.hs
@@ -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.
diff --git a/test/AggregateFunctionsTest.hs b/test/AggregateFunctionsTest.hs
--- a/test/AggregateFunctionsTest.hs
+++ b/test/AggregateFunctionsTest.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE BlockArguments #-}
 {-# LANGUAGE ForeignFunctionInterface #-}
+{-# OPTIONS_GHC -Wno-deprecations #-}
 
 module AggregateFunctionsTest (tests) where
 
diff --git a/test/AppenderTest.hs b/test/AppenderTest.hs
--- a/test/AppenderTest.hs
+++ b/test/AppenderTest.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE BlockArguments #-}
 {-# LANGUAGE TypeApplications #-}
+{-# OPTIONS_GHC -Wno-deprecations #-}
 
 module AppenderTest (tests) where
 
diff --git a/test/ArrowInterfaceDeprecatedTests.hs b/test/ArrowInterfaceDeprecatedTests.hs
--- a/test/ArrowInterfaceDeprecatedTests.hs
+++ b/test/ArrowInterfaceDeprecatedTests.hs
@@ -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 =
diff --git a/test/ArrowInterfaceTest.hs b/test/ArrowInterfaceTest.hs
--- a/test/ArrowInterfaceTest.hs
+++ b/test/ArrowInterfaceTest.hs
@@ -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 ()
diff --git a/test/BindValuesTest.hs b/test/BindValuesTest.hs
--- a/test/BindValuesTest.hs
+++ b/test/BindValuesTest.hs
@@ -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
diff --git a/test/CastFunctionsTest.hs b/test/CastFunctionsTest.hs
--- a/test/CastFunctionsTest.hs
+++ b/test/CastFunctionsTest.hs
@@ -1,6 +1,7 @@
 {-# LANGUAGE BlockArguments #-}
 {-# LANGUAGE ForeignFunctionInterface #-}
 {-# LANGUAGE NamedFieldPuns #-}
+{-# OPTIONS_GHC -Wno-deprecations #-}
 
 module CastFunctionsTest (tests) where
 
diff --git a/test/ExecutePreparedStatementsTest.hs b/test/ExecutePreparedStatementsTest.hs
--- a/test/ExecutePreparedStatementsTest.hs
+++ b/test/ExecutePreparedStatementsTest.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE BlockArguments #-}
+{-# OPTIONS_GHC -Wno-deprecations #-}
 
 module ExecutePreparedStatementsTest (tests) where
 
diff --git a/test/ExtractStatementsTest.hs b/test/ExtractStatementsTest.hs
--- a/test/ExtractStatementsTest.hs
+++ b/test/ExtractStatementsTest.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE BlockArguments #-}
+{-# OPTIONS_GHC -Wno-deprecations #-}
 
 module ExtractStatementsTest (tests) where
 
diff --git a/test/LogicalTypesTest.hs b/test/LogicalTypesTest.hs
--- a/test/LogicalTypesTest.hs
+++ b/test/LogicalTypesTest.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE BlockArguments #-}
+{-# OPTIONS_GHC -Wno-deprecations #-}
 
 module LogicalTypesTest (tests) where
 
diff --git a/test/OpenConnectTest.hs b/test/OpenConnectTest.hs
--- a/test/OpenConnectTest.hs
+++ b/test/OpenConnectTest.hs
@@ -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 =
diff --git a/test/PendingResultTest.hs b/test/PendingResultTest.hs
--- a/test/PendingResultTest.hs
+++ b/test/PendingResultTest.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE BlockArguments #-}
+{-# OPTIONS_GHC -Wno-deprecations #-}
 
 module PendingResultTest (tests) where
 
diff --git a/test/QueryExecutionTest.hs b/test/QueryExecutionTest.hs
--- a/test/QueryExecutionTest.hs
+++ b/test/QueryExecutionTest.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE BlockArguments #-}
+{-# OPTIONS_GHC -Wno-deprecations #-}
 
 module QueryExecutionTest (tests) where
 
diff --git a/test/ReplacementScansTest.hs b/test/ReplacementScansTest.hs
--- a/test/ReplacementScansTest.hs
+++ b/test/ReplacementScansTest.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE BlockArguments #-}
+{-# OPTIONS_GHC -Wno-deprecations #-}
 
 module ReplacementScansTest (tests) where
 
diff --git a/test/ResultFunctionsTest.hs b/test/ResultFunctionsTest.hs
--- a/test/ResultFunctionsTest.hs
+++ b/test/ResultFunctionsTest.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE BlockArguments #-}
+{-# OPTIONS_GHC -Wno-deprecations #-}
 
 module ResultFunctionsTest (tests) where
 
diff --git a/test/SafeFetchTest.hs b/test/SafeFetchTest.hs
--- a/test/SafeFetchTest.hs
+++ b/test/SafeFetchTest.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE BlockArguments #-}
+{-# OPTIONS_GHC -Wno-deprecations #-}
 
 module SafeFetchTest (tests) where
 
diff --git a/test/ScalarFunctionsTest.hs b/test/ScalarFunctionsTest.hs
--- a/test/ScalarFunctionsTest.hs
+++ b/test/ScalarFunctionsTest.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE BlockArguments #-}
 {-# LANGUAGE ForeignFunctionInterface #-}
+{-# OPTIONS_GHC -Wno-deprecations #-}
 
 module ScalarFunctionsTest (tests) where
 
diff --git a/test/StreamingResultTest.hs b/test/StreamingResultTest.hs
--- a/test/StreamingResultTest.hs
+++ b/test/StreamingResultTest.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE BlockArguments #-}
+{-# OPTIONS_GHC -Wno-deprecations #-}
 
 module StreamingResultTest (tests) where
 
diff --git a/test/TableFunctionsTest.hs b/test/TableFunctionsTest.hs
--- a/test/TableFunctionsTest.hs
+++ b/test/TableFunctionsTest.hs
@@ -1,6 +1,7 @@
 {-# LANGUAGE BlockArguments #-}
 {-# LANGUAGE ForeignFunctionInterface #-}
 {-# LANGUAGE RecordWildCards #-}
+{-# OPTIONS_GHC -Wno-deprecations #-}
 
 module TableFunctionsTest (tests) where
 
diff --git a/test/ThreadingTest.hs b/test/ThreadingTest.hs
--- a/test/ThreadingTest.hs
+++ b/test/ThreadingTest.hs
@@ -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
 
diff --git a/test/VectorTest.hs b/test/VectorTest.hs
--- a/test/VectorTest.hs
+++ b/test/VectorTest.hs
@@ -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
