Skip to main content

better_duck_core/raw/
expression.rs

1//! RAII wrapper for a bound `duckdb_expression`, with constant folding.
2//!
3//! DuckDB hands a bound expression to a scalar function's bind callback (one per
4//! argument, via `duckdb_scalar_function_bind_get_argument`). The handle is owned —
5//! it must be destroyed with `duckdb_destroy_expression` — so [`Expression`] wraps it
6//! in RAII. It exposes the expression's return type, whether it is *foldable* into a
7//! constant, and [`fold`](Expression::fold), which evaluates a foldable expression to
8//! a value (e.g. to specialise a function on a constant argument at bind time).
9//!
10//! `duckdb_expression_fold` returns a `duckdb_error_data`, read through the same
11//! [`ErrorData`] RAII wrapper the rest of the driver uses, so a fold failure surfaces
12//! as a typed [`EngineError`] rather than a bare string.
13// FFI pointer args are used safely inside `unsafe` blocks.
14#![allow(clippy::not_unsafe_ptr_arg_deref)]
15
16use std::ffi::{c_void, CStr};
17
18use crate::{
19    error::{EngineError, EngineErrorKind},
20    ffi::{
21        duckdb_destroy_expression, duckdb_destroy_value, duckdb_expression, duckdb_expression_fold,
22        duckdb_expression_is_foldable, duckdb_expression_return_type, duckdb_free, duckdb_get_bool,
23        duckdb_get_double, duckdb_get_int32, duckdb_get_int64, duckdb_get_type_id,
24        duckdb_get_value_type, duckdb_get_varchar, duckdb_value, DUCKDB_TYPE_DUCKDB_TYPE_BIGINT,
25        DUCKDB_TYPE_DUCKDB_TYPE_BOOLEAN, DUCKDB_TYPE_DUCKDB_TYPE_DOUBLE,
26        DUCKDB_TYPE_DUCKDB_TYPE_INTEGER, DUCKDB_TYPE_DUCKDB_TYPE_VARCHAR,
27    },
28    raw::{client_context::ClientContext, error_data::ErrorData},
29    types::{value::DuckValue, LogicalType},
30};
31
32/// An owned, bound `duckdb_expression` (destroyed once on drop).
33pub struct Expression {
34    expr: duckdb_expression,
35}
36
37impl Expression {
38    /// Takes ownership of a raw expression handle, or `None` if null.
39    ///
40    /// # Safety
41    ///
42    /// `expr` must be a live `duckdb_expression` whose ownership is transferred here
43    /// (destroyed on drop).
44    pub(crate) unsafe fn from_raw(expr: duckdb_expression) -> Option<Expression> {
45        if expr.is_null() {
46            return None;
47        }
48        Some(Expression { expr })
49    }
50
51    /// The expression's return type, or `None` if DuckDB reports none.
52    #[must_use]
53    pub fn return_type(&self) -> Option<LogicalType> {
54        // SAFETY: `self.expr` is a valid expression; the returned logical type is
55        // owned (destroy once) and wrapped in RAII. Null → None.
56        LogicalType::from_raw(unsafe { duckdb_expression_return_type(self.expr) }).ok()
57    }
58
59    /// Whether the expression can be folded into a constant value.
60    #[must_use]
61    pub fn is_foldable(&self) -> bool {
62        // SAFETY: `self.expr` is a valid expression.
63        unsafe { duckdb_expression_is_foldable(self.expr) }
64    }
65
66    /// Folds a foldable expression into a scalar [`DuckValue`] using `context`.
67    ///
68    /// # Errors
69    ///
70    /// Returns an [`EngineError`] if DuckDB reports a fold error, or if the folded
71    /// value is of a type this scalar extractor does not cover.
72    pub fn fold(
73        &self,
74        context: &ClientContext<'_>,
75    ) -> Result<DuckValue, EngineError> {
76        let mut out: duckdb_value = std::ptr::null_mut();
77        // SAFETY: `context` and `self.expr` are valid; `out` is a valid out-pointer.
78        // `duckdb_expression_fold` returns an owned error-data handle (read via RAII)
79        // and, on success, writes an owned `duckdb_value` into `out`.
80        let err = unsafe { duckdb_expression_fold(context.as_raw(), self.expr, &mut out) };
81        // SAFETY: `err` is an owned error-data handle (or null); `ErrorData` owns it.
82        if let Some(data) = unsafe { ErrorData::from_raw(err) } {
83            if data.has_error() {
84                if !out.is_null() {
85                    // SAFETY: `out` was written by fold; destroy exactly once.
86                    unsafe { duckdb_destroy_value(&mut out) };
87                }
88                return Err(data.to_engine_error());
89            }
90        }
91        if out.is_null() {
92            return Err(EngineError {
93                kind: EngineErrorKind::Unavailable,
94                message: Some("expression folded to no value".to_owned()),
95            });
96        }
97        // SAFETY: `out` is an owned scalar value; the helper reads and destroys it.
98        unsafe { scalar_value_to_duckvalue(out) }
99    }
100}
101
102/// Reads an owned scalar `duckdb_value` into a [`DuckValue`], destroying it.
103///
104/// # Safety
105///
106/// `value` must be an owned, non-null `duckdb_value`; ownership is taken here.
107unsafe fn scalar_value_to_duckvalue(mut value: duckdb_value) -> Result<DuckValue, EngineError> {
108    // SAFETY: `value` is valid; `duckdb_get_value_type` returns a *borrowed* logical
109    // type owned by `value` (must NOT be destroyed); we only read its type id.
110    let type_id = unsafe { duckdb_get_type_id(duckdb_get_value_type(value)) };
111    // SAFETY: `value` is a valid scalar value of the type just read.
112    let result = unsafe {
113        match type_id {
114            DUCKDB_TYPE_DUCKDB_TYPE_BOOLEAN => Ok(DuckValue::Boolean(duckdb_get_bool(value))),
115            DUCKDB_TYPE_DUCKDB_TYPE_INTEGER => Ok(DuckValue::Int(duckdb_get_int32(value))),
116            DUCKDB_TYPE_DUCKDB_TYPE_BIGINT => Ok(DuckValue::BigInt(duckdb_get_int64(value))),
117            DUCKDB_TYPE_DUCKDB_TYPE_DOUBLE => Ok(DuckValue::Double(duckdb_get_double(value))),
118            DUCKDB_TYPE_DUCKDB_TYPE_VARCHAR => {
119                let c = duckdb_get_varchar(value);
120                if c.is_null() {
121                    Ok(DuckValue::Text(String::new()))
122                } else {
123                    let s = CStr::from_ptr(c).to_string_lossy().into_owned();
124                    duckdb_free(c as *mut c_void);
125                    Ok(DuckValue::Text(s))
126                }
127            },
128            other => Err(EngineError {
129                kind: EngineErrorKind::Unavailable,
130                message: Some(format!("folded value type {other} is not supported")),
131            }),
132        }
133    };
134    // SAFETY: `value` is owned here; destroy exactly once.
135    unsafe { duckdb_destroy_value(&mut value) };
136    result
137}
138
139impl Drop for Expression {
140    fn drop(&mut self) {
141        if !self.expr.is_null() {
142            // SAFETY: `self.expr` is a valid, non-null expression owned by `self` and
143            // not yet destroyed; destroyed exactly once here.
144            unsafe { duckdb_destroy_expression(&mut self.expr) };
145        }
146    }
147}