packages feed

duckdb-ffi-1.4.1.2: test/ExtractStatementsTest.hs

{-# LANGUAGE BlockArguments #-}

module ExtractStatementsTest (tests) where

import Control.Monad (forM_, when)
import Data.Int (Int64)
import Database.DuckDB.FFI
import Database.DuckDB.FFI.Deprecated
import Foreign.C.String (peekCString, withCString)
import Foreign.Marshal.Alloc (alloca)
import Foreign.Ptr (nullPtr)
import Foreign.Storable (peek)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertBool, assertFailure, testCase, (@?=))
import Utils (withConnection, withDatabase)

tests :: TestTree
tests =
    testGroup
        "Extract Statements"
        [ extractPrepareAndExecuteSequence
        , extractFailureYieldsErrorMessage
        ]

extractPrepareAndExecuteSequence :: TestTree
extractPrepareAndExecuteSequence =
    testCase "extract, prepare, and execute a multi-statement script" $
        withDatabase \db ->
            withConnection db \conn -> do
                let script =
                        "CREATE TABLE extracted_values(val INTEGER);"
                            <> "INSERT INTO extracted_values VALUES (1), (2);"
                            <> "SELECT SUM(val) FROM extracted_values;"

                withCString script \scriptPtr ->
                    alloca \exPtr -> do
                        stmtCount <- c_duckdb_extract_statements conn scriptPtr exPtr
                        stmtCount @?= 3

                        extracted <- peek exPtr
                        assertBool "extracted handle should not be null" (extracted /= nullPtr)

                        let indices = [0 :: Integer .. fromIntegral stmtCount - 1]
                        forM_ indices \idx ->
                            alloca \stmtPtr -> do
                                let duckIdx = fromIntegral idx
                                prepState <- c_duckdb_prepare_extracted_statement conn extracted duckIdx stmtPtr
                                prepState @?= DuckDBSuccess

                                stmt <- peek stmtPtr
                                assertBool "prepared statement should not be null" (stmt /= nullPtr)

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

                                    case idx of
                                        0 -> do
                                            resultType <- c_duckdb_result_return_type resPtr
                                            resultType @?= DuckDBResultTypeNothing
                                        1 -> do
                                            resultType <- c_duckdb_result_return_type resPtr
                                            resultType @?= DuckDBResultTypeChangedRows
                                        2 -> do
                                            rowCount <- c_duckdb_row_count resPtr
                                            rowCount @?= 1
                                            total <- c_duckdb_value_int64 resPtr 0 0
                                            (total :: Int64) @?= 3
                                        _ -> pure ()

                                    c_duckdb_destroy_result resPtr

                                c_duckdb_destroy_prepare stmtPtr

                        -- Preparing out-of-range should fail with an informative error
                        alloca \stmtPtr -> do
                            let invalidIdx = stmtCount
                            stInvalid <- c_duckdb_prepare_extracted_statement conn extracted invalidIdx stmtPtr
                            stInvalid @?= DuckDBError
                            stmt <- peek stmtPtr
                            assertBool "invalid index should not yield a statement handle" (stmt == nullPtr)

                        c_duckdb_destroy_extracted exPtr

extractFailureYieldsErrorMessage :: TestTree
extractFailureYieldsErrorMessage =
    testCase "failed extract surfaces parser error" $
        withDatabase \db ->
            withConnection db \conn -> do
                withCString "SELECT * FROM invalid_table WHERE" \badSql ->
                    alloca \exPtr -> do
                        stmtCount <- c_duckdb_extract_statements conn badSql exPtr
                        stmtCount @?= 0

                        extracted <- peek exPtr
                        assertBool "extracted handle should be available for errors" (extracted /= nullPtr)

                        errPtr <- c_duckdb_extract_statements_error extracted
                        when (errPtr == nullPtr) $
                            assertFailure "expected error message pointer from failed extract"
                        errMsg <- peekCString errPtr
                        assertBool "error message should not be empty" (not (null errMsg))

                        c_duckdb_destroy_extracted exPtr