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}