packages feed

duckdb-simple-0.1.5.0: src/Database/DuckDB/Simple/Catalog.hs

{-# LANGUAGE BlockArguments #-}

{- |
Module      : Database.DuckDB.Simple.Catalog
Description : High-level helpers for DuckDB catalog inspection.
-}
module Database.DuckDB.Simple.Catalog (
    CatalogEntry (..),
    catalogTypeName,
    lookupCatalogEntry,
) where

import Control.Exception (bracket)
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Foreign as TextForeign
import Database.DuckDB.FFI
import Database.DuckDB.Simple.Internal (Connection, withClientContext)
import Foreign.C.String (CString, peekCString)
import Foreign.Marshal.Alloc (alloca)
import Foreign.Ptr (nullPtr)
import Foreign.Storable (poke)

-- | A simplified view of a catalog entry returned by DuckDB.
data CatalogEntry = CatalogEntry
    { catalogEntryName :: !Text
    , catalogEntryType :: !DuckDBCatalogEntryType
    }
    deriving (Eq, Show)

-- | Look up the backend type name of a named catalog.
catalogTypeName :: Connection -> Text -> IO (Maybe Text)
catalogTypeName conn catalogName =
    withClientContext conn \ctx ->
        TextForeign.withCString catalogName \cName ->
            withMaybeCatalog ctx cName \catalog -> do
                namePtr <- c_duckdb_catalog_get_type_name catalog
                if namePtr == nullPtr
                    then pure Nothing
                    else Just . Text.pack <$> peekCString namePtr

-- | Look up a catalog entry by catalog, schema, name, and expected entry kind.
lookupCatalogEntry :: Connection -> Text -> Text -> Text -> DuckDBCatalogEntryType -> IO (Maybe CatalogEntry)
lookupCatalogEntry conn catalogName schemaName entryName entryType =
    withClientContext conn \ctx ->
        TextForeign.withCString catalogName \cCatalog ->
            TextForeign.withCString schemaName \cSchema ->
                TextForeign.withCString entryName \cEntry ->
                    withMaybeCatalog ctx cCatalog \catalog ->
                        withMaybeCatalogEntry catalog ctx entryType cSchema cEntry \entry -> do
                            typ <- c_duckdb_catalog_entry_get_type entry
                            namePtr <- c_duckdb_catalog_entry_get_name entry
                            if namePtr == nullPtr
                                then pure Nothing
                                else do
                                    name <- Text.pack <$> peekCString namePtr
                                    pure (Just CatalogEntry{catalogEntryName = name, catalogEntryType = typ})

destroyCatalog :: DuckDBCatalog -> IO ()
destroyCatalog catalog =
    alloca \ptr -> poke ptr catalog >> c_duckdb_destroy_catalog ptr

destroyCatalogEntry :: DuckDBCatalogEntry -> IO ()
destroyCatalogEntry entry =
    alloca \ptr -> poke ptr entry >> c_duckdb_destroy_catalog_entry ptr

withMaybeCatalog :: DuckDBClientContext -> CString -> (DuckDBCatalog -> IO (Maybe a)) -> IO (Maybe a)
withMaybeCatalog ctx name action = do
    catalog <- c_duckdb_client_context_get_catalog ctx name
    if catalog == nullPtr
        then pure Nothing
        else bracket (pure catalog) destroyCatalog action

withMaybeCatalogEntry ::
    DuckDBCatalog ->
    DuckDBClientContext ->
    DuckDBCatalogEntryType ->
    CString ->
    CString ->
    (DuckDBCatalogEntry -> IO (Maybe a)) ->
    IO (Maybe a)
withMaybeCatalogEntry catalog ctx entryType schemaName entryName action = do
    entry <- c_duckdb_catalog_get_entry catalog ctx entryType schemaName entryName
    if entry == nullPtr
        then pure Nothing
        else bracket (pure entry) destroyCatalogEntry action