packages feed

duckdb-ffi-0.1.4.1: test/ExecutePreparedStatementsTest.hs

{-# LANGUAGE BlockArguments #-}

module ExecutePreparedStatementsTest (tests) where

import Control.Monad (forM_)
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, (@?=))
import Utils (withConnection, withDatabase)

tests :: TestTree
tests =
    testGroup
        "Execute Prepared Statements"
        [ executePreparedProducesResult
        , executePreparedStreamingProducesChunks
        ]

setupTable :: DuckDBConnection -> IO ()
setupTable conn = do
    let statements =
            [ "CREATE TABLE exec_prepared (id INTEGER, name VARCHAR);"
            , "INSERT INTO exec_prepared VALUES (1, 'alpha'), (2, 'beta');"
            ]
    forM_ statements \sql ->
        withCString sql \cSql ->
            alloca \resPtr -> do
                st <- c_duckdb_query conn cSql resPtr
                st @?= DuckDBSuccess
                c_duckdb_destroy_result resPtr

executePreparedProducesResult :: TestTree
executePreparedProducesResult =
    testCase "execute_prepared returns regular result" $
        withDatabase \db ->
            withConnection db \conn -> do
                setupTable conn

                withCString "SELECT name FROM exec_prepared WHERE id = ?" \querySql ->
                    alloca \stmtPtr -> do
                        st <- c_duckdb_prepare conn querySql stmtPtr
                        st @?= DuckDBSuccess
                        stmt <- peek stmtPtr
                        assertBool "prepared statement should not be null" (stmt /= nullPtr)

                        c_duckdb_bind_int32 stmt 1 2 >>= (@?= DuckDBSuccess)

                        alloca \resPtr -> do
                            execState <- c_duckdb_execute_prepared stmt resPtr
                            execState @?= DuckDBSuccess

                            streamingFlag <- c_duckdb_result_is_streaming resPtr
                            streamingFlag @?= CBool 0

                            rowCount <- c_duckdb_row_count resPtr
                            rowCount @?= 1

                            varcharPtr <- c_duckdb_value_varchar resPtr 0 0
                            peekCString varcharPtr >>= (@?= "beta")
                            c_duckdb_free (castPtr varcharPtr)

                            c_duckdb_destroy_result resPtr

                        c_duckdb_destroy_prepare stmtPtr

executePreparedStreamingProducesChunks :: TestTree
executePreparedStreamingProducesChunks =
    testCase "execute_prepared_streaming produces streaming result" $
        withDatabase \db ->
            withConnection db \conn -> do
                setupTable conn

                withCString "SELECT id, name FROM exec_prepared ORDER BY id" \querySql ->
                    alloca \stmtPtr -> do
                        st <- c_duckdb_prepare conn querySql stmtPtr
                        st @?= DuckDBSuccess
                        stmt <- peek stmtPtr
                        assertBool "streaming prepared statement should not be null" (stmt /= nullPtr)

                        alloca \resPtr -> do
                            execState <- c_duckdb_execute_prepared_streaming stmt resPtr
                            execState @?= DuckDBSuccess

                            streamingFlag <- c_duckdb_result_is_streaming resPtr
                            streamingFlag @?= CBool 1

                            chunk <- c_duckdb_stream_fetch_chunk resPtr
                            assertBool "streaming fetch chunk returns data" (chunk /= nullPtr)

                            chunkSize <- c_duckdb_data_chunk_get_size chunk
                            assertBool "streamed chunk should contain rows" (chunkSize > 0)

                            alloca \chunkPtr -> do
                                poke chunkPtr chunk
                                c_duckdb_destroy_data_chunk chunkPtr

                            c_duckdb_destroy_result resPtr

                        c_duckdb_destroy_prepare stmtPtr