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