use std::ffi::CStr;
use libduckdb_sys::{
duckdb_catalog, duckdb_catalog_entry, duckdb_catalog_entry_get_name,
duckdb_catalog_entry_get_type, duckdb_catalog_entry_type,
duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_COLLATION,
duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_DATABASE,
duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_INDEX,
duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_INVALID,
duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_PREPARED_STATEMENT,
duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_SCHEMA,
duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_SEQUENCE,
duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_TABLE,
duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_TYPE,
duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_VIEW, duckdb_catalog_get_entry,
duckdb_client_context, duckdb_destroy_catalog, duckdb_destroy_catalog_entry,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CatalogEntryType {
Invalid,
Table,
View,
Index,
Schema,
PreparedStatement,
Sequence,
Collation,
Type,
Database,
}
impl CatalogEntryType {
#[must_use]
pub(crate) const fn to_raw(self) -> duckdb_catalog_entry_type {
match self {
Self::Invalid => duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_INVALID,
Self::Table => duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_TABLE,
Self::View => duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_VIEW,
Self::Index => duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_INDEX,
Self::Schema => duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_SCHEMA,
Self::PreparedStatement => {
duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_PREPARED_STATEMENT
}
Self::Sequence => duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_SEQUENCE,
Self::Collation => duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_COLLATION,
Self::Type => duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_TYPE,
Self::Database => duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_DATABASE,
}
}
#[must_use]
pub(crate) const fn from_raw(raw: duckdb_catalog_entry_type) -> Self {
match raw {
x if x == duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_TABLE => Self::Table,
x if x == duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_VIEW => Self::View,
x if x == duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_INDEX => Self::Index,
x if x == duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_SCHEMA => Self::Schema,
x if x == duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_PREPARED_STATEMENT => {
Self::PreparedStatement
}
x if x == duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_SEQUENCE => {
Self::Sequence
}
x if x == duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_COLLATION => {
Self::Collation
}
x if x == duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_TYPE => Self::Type,
x if x == duckdb_catalog_entry_type_DUCKDB_CATALOG_ENTRY_TYPE_DATABASE => {
Self::Database
}
_ => Self::Invalid,
}
}
}
pub struct CatalogEntry {
entry: duckdb_catalog_entry,
}
impl CatalogEntry {
pub unsafe fn lookup(
catalog: duckdb_catalog,
context: duckdb_client_context,
schema: &CStr,
name: &CStr,
entry_type: CatalogEntryType,
) -> Option<Self> {
let entry = unsafe {
duckdb_catalog_get_entry(
catalog,
context,
entry_type.to_raw(),
schema.as_ptr(),
name.as_ptr(),
)
};
if entry.is_null() {
None
} else {
Some(Self { entry })
}
}
#[must_use]
pub fn name(&self) -> Option<&str> {
let ptr = unsafe { duckdb_catalog_entry_get_name(self.entry) };
if ptr.is_null() {
return None;
}
unsafe { CStr::from_ptr(ptr) }.to_str().ok()
}
#[must_use]
pub fn entry_type(&self) -> CatalogEntryType {
let raw = unsafe { duckdb_catalog_entry_get_type(self.entry) };
CatalogEntryType::from_raw(raw)
}
}
impl Drop for CatalogEntry {
fn drop(&mut self) {
unsafe {
duckdb_destroy_catalog_entry(&raw mut self.entry);
}
}
}
pub struct Catalog {
catalog: duckdb_catalog,
}
impl Catalog {
pub(crate) const unsafe fn from_raw(catalog: duckdb_catalog) -> Self {
Self { catalog }
}
#[must_use]
pub const fn as_raw(&self) -> duckdb_catalog {
self.catalog
}
pub unsafe fn get_entry(
&self,
context: duckdb_client_context,
schema: &CStr,
name: &CStr,
entry_type: CatalogEntryType,
) -> Option<CatalogEntry> {
unsafe { CatalogEntry::lookup(self.catalog, context, schema, name, entry_type) }
}
}
impl Drop for Catalog {
fn drop(&mut self) {
unsafe {
duckdb_destroy_catalog(&raw mut self.catalog);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn catalog_entry_type_round_trip_all_variants() {
let variants = [
CatalogEntryType::Invalid,
CatalogEntryType::Table,
CatalogEntryType::View,
CatalogEntryType::Index,
CatalogEntryType::Schema,
CatalogEntryType::PreparedStatement,
CatalogEntryType::Sequence,
CatalogEntryType::Collation,
CatalogEntryType::Type,
CatalogEntryType::Database,
];
for variant in variants {
let raw = variant.to_raw();
let back = CatalogEntryType::from_raw(raw);
assert_eq!(variant, back, "round-trip failed for {variant:?}");
}
}
#[test]
fn catalog_entry_type_unknown_raw_maps_to_invalid() {
let result = CatalogEntryType::from_raw(9999);
assert_eq!(result, CatalogEntryType::Invalid);
}
#[test]
fn catalog_entry_type_is_copy_and_eq() {
let a = CatalogEntryType::Table;
let b = a;
assert_eq!(a, b);
}
#[test]
fn catalog_entry_type_debug_impl() {
let s = format!("{:?}", CatalogEntryType::View);
assert_eq!(s, "View");
}
#[test]
fn catalog_entry_type_distinct_raw_values() {
let variants = [
CatalogEntryType::Invalid,
CatalogEntryType::Table,
CatalogEntryType::View,
CatalogEntryType::Index,
CatalogEntryType::Schema,
CatalogEntryType::PreparedStatement,
CatalogEntryType::Sequence,
CatalogEntryType::Collation,
CatalogEntryType::Type,
CatalogEntryType::Database,
];
let raws: Vec<duckdb_catalog_entry_type> = variants.iter().map(|v| v.to_raw()).collect();
for (i, a) in raws.iter().enumerate().skip(1) {
for b in raws.iter().skip(i + 1) {
assert_ne!(a, b, "two non-Invalid variants share raw value {a}");
}
}
}
}