packages feed

duckdb-ffi-0.1.4.1: test/OpenConnectTest.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE RecordWildCards #-}

module OpenConnectTest (tests) where

import Control.Monad (when)
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 Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertBool, testCase, (@?=))

tests :: TestTree
tests =
    testGroup
        "Open Connect"
        [ testLibraryVersion
        , testOpenAndConnect
        , testOpenExtWithConfig
        , testInstanceCache
        ]

testLibraryVersion :: TestTree
testLibraryVersion =
    testCase "duckdb_library_version returns a non-empty string" $ do
        versionPtr <- c_duckdb_library_version
        version <- peekCString versionPtr
        assertBool "library version should not be empty" (not (null version))

testOpenAndConnect :: TestTree
testOpenAndConnect =
    testCase "open, connect, and gather connection details" $
        withCString ":memory:" $ \path ->
            alloca $ \dbPtr -> do
                state <- c_duckdb_open path dbPtr
                state @?= DuckDBSuccess
                db <- peek dbPtr

                alloca $ \connPtr -> do
                    connState <- c_duckdb_connect db connPtr
                    connState @?= DuckDBSuccess
                    conn <- peek connPtr

                    -- Query progress information (should succeed even without a running query)
                    alloca $ \progressPtr -> do
                        poke progressPtr (DuckDBQueryProgress 0 0 0)
                        c_duckdb_query_progress conn progressPtr
                        DuckDBQueryProgress{..} <- peek progressPtr
                        assertBool "rows processed should be non-negative" (duckDBQueryProgressRowsProcessed >= 0 && duckDBQueryProgressTotalRows >= 0)

                    -- Interrupt is a no-op but should succeed
                    c_duckdb_interrupt conn

                    -- Retrieve client context and confirm it points back to the same connection
                    alloca $ \ctxPtr -> do
                        poke ctxPtr nullPtr
                        c_duckdb_connection_get_client_context conn ctxPtr
                        ctx <- peek ctxPtr
                        assertBool "client context should not be null" (ctx /= nullPtr)
                        cid <- c_duckdb_client_context_get_connection_id ctx
                        assertBool "connection id should be non-negative" (cid >= 0)
                        c_duckdb_destroy_client_context ctxPtr

                    -- Retrieve arrow options object
                    alloca $ \arrowPtr -> do
                        poke arrowPtr nullPtr
                        c_duckdb_connection_get_arrow_options conn arrowPtr
                        arrowOpts <- peek arrowPtr
                        assertBool "arrow options should not be null" (arrowOpts /= nullPtr)
                        c_duckdb_destroy_arrow_options arrowPtr

                    -- Fetch table names (there should be none, but the call should succeed)
                    alloca $ \valuePtr -> do
                        tablesValue <- withCString "%" $ \filterStr -> c_duckdb_get_table_names conn filterStr (CBool 0)
                        poke valuePtr tablesValue
                        c_duckdb_destroy_value valuePtr

                    -- Disconnect and close database
                    c_duckdb_disconnect connPtr

                c_duckdb_close dbPtr

testOpenExtWithConfig :: TestTree
testOpenExtWithConfig =
    testCase "open_ext with configuration" $
        withCString ":memory:" $ \path ->
            alloca $ \configPtr -> do
                cfgState <- c_duckdb_create_config configPtr
                cfgState @?= DuckDBSuccess
                config <- peek configPtr

                alloca $ \errorPtr -> do
                    poke errorPtr nullPtr
                    alloca $ \dbPtr -> do
                        state <- c_duckdb_open_ext path dbPtr config errorPtr
                        state @?= DuckDBSuccess

                        errMsgPtr <- peek errorPtr
                        when (errMsgPtr /= nullPtr) $
                            c_duckdb_free (castPtr errMsgPtr)

                        c_duckdb_close dbPtr

                c_duckdb_destroy_config configPtr

testInstanceCache :: TestTree
testInstanceCache =
    testCase "create instance cache and reuse database handle" $
        withCString ":memory:" $ \path ->
            alloca $ \configPtr -> do
                cfgState <- c_duckdb_create_config configPtr
                cfgState @?= DuckDBSuccess
                config <- peek configPtr

                cache <- c_duckdb_create_instance_cache
                assertBool "instance cache should not be null" (cache /= nullPtr)

                alloca $ \dbPtr -> do
                    alloca $ \errorPtr -> do
                        poke errorPtr nullPtr
                        state <- c_duckdb_get_or_create_from_cache cache path dbPtr config errorPtr
                        state @?= DuckDBSuccess
                        err <- peek errorPtr
                        when (err /= nullPtr) $
                            c_duckdb_free (castPtr err)

                        -- the database returned from the cache should be usable (we close it immediately)
                        c_duckdb_close dbPtr

                alloca $ \cachePtr -> do
                    poke cachePtr cache
                    c_duckdb_destroy_instance_cache cachePtr

                c_duckdb_destroy_config configPtr