Skip to main content

quack_rs/
expression.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2026 Tom F. <https://github.com/tomtom215/>
3// My way of giving something small back to the open source community
4// and encouraging more Rust development!
5
6//! Bound expressions (`DuckDB` 1.5.0+).
7//!
8//! [`Expression`] is an RAII wrapper around `DuckDB`'s `duckdb_expression` handle.
9//! Extension authors obtain one from a scalar function's *bind* callback via
10//! [`ScalarBindInfo::argument`][crate::scalar::ScalarBindInfo::argument], which
11//! lets the bind phase inspect each argument's static type and — when the
12//! argument is a constant — fold it to a concrete [`Value`].
13//!
14//! This is the canonical way to implement scalar functions whose behaviour
15//! depends on a constant argument (for example a format string or a precision)
16//! that should be validated or pre-computed once at bind time rather than on
17//! every row.
18//!
19//! # Example
20//!
21//! ```rust,no_run
22//! use quack_rs::scalar::ScalarBindInfo;
23//! use libduckdb_sys::duckdb_bind_info;
24//!
25//! unsafe extern "C" fn my_bind(info: duckdb_bind_info) {
26//!     let bind = unsafe { ScalarBindInfo::new(info) };
27//!     if let Some(arg) = unsafe { bind.argument(0) } {
28//!         // Inspect the argument's static return type at bind time.
29//!         let _ty = arg.return_type();
30//!         if arg.is_foldable() {
31//!             // With a `ClientContext`, `arg.fold(&ctx)` pre-computes the constant.
32//!         }
33//!     }
34//! }
35//! ```
36
37use libduckdb_sys::{
38    duckdb_destroy_expression, duckdb_expression, duckdb_expression_fold,
39    duckdb_expression_is_foldable, duckdb_expression_return_type, duckdb_value,
40};
41
42use crate::client_context::ClientContext;
43use crate::error_data::ErrorData;
44use crate::types::LogicalType;
45use crate::value::Value;
46
47/// RAII wrapper for a `duckdb_expression`.
48///
49/// Automatically destroyed when dropped.
50pub struct Expression {
51    raw: duckdb_expression,
52}
53
54impl Expression {
55    /// Wraps a raw `duckdb_expression` handle, taking ownership.
56    ///
57    /// # Safety
58    ///
59    /// `raw` must be a valid `duckdb_expression` returned by a `DuckDB` API call
60    /// (e.g. `duckdb_scalar_function_bind_get_argument`). The caller must not
61    /// destroy the handle after this call.
62    #[inline]
63    #[must_use]
64    pub const unsafe fn from_raw(raw: duckdb_expression) -> Self {
65        Self { raw }
66    }
67
68    /// Returns the raw handle without consuming the `Expression`.
69    #[inline]
70    #[must_use]
71    pub const fn as_raw(&self) -> duckdb_expression {
72        self.raw
73    }
74
75    /// Returns `true` if the underlying handle is null.
76    #[inline]
77    #[must_use]
78    pub const fn is_null(&self) -> bool {
79        self.raw.is_null()
80    }
81
82    /// Returns the static return type of this expression, or `None` if the
83    /// handle is null.
84    #[must_use]
85    pub fn return_type(&self) -> Option<LogicalType> {
86        if self.raw.is_null() {
87            return None;
88        }
89        // SAFETY: self.raw is a non-null, valid duckdb_expression. The returned
90        // logical type is owned by the caller and freed by LogicalType on drop.
91        let raw = unsafe { duckdb_expression_return_type(self.raw) };
92        if raw.is_null() {
93            return None;
94        }
95        // SAFETY: raw is a non-null logical type handle owned by the caller.
96        Some(unsafe { LogicalType::from_raw(raw) })
97    }
98
99    /// Returns `true` if this expression is *foldable* — i.e. it is constant and
100    /// can be evaluated to a single [`Value`] via [`fold`][Expression::fold]
101    /// without per-row input.
102    #[must_use]
103    pub fn is_foldable(&self) -> bool {
104        if self.raw.is_null() {
105            return false;
106        }
107        // SAFETY: self.raw is a non-null, valid duckdb_expression.
108        unsafe { duckdb_expression_is_foldable(self.raw) }
109    }
110
111    /// Folds this (constant) expression into a single [`Value`].
112    ///
113    /// Only valid when [`is_foldable`][Expression::is_foldable] returns `true`.
114    ///
115    /// # Errors
116    ///
117    /// Returns the structured [`ErrorData`] if folding fails (for example because
118    /// the expression is not constant).
119    pub fn fold(&self, context: &ClientContext) -> Result<Value, ErrorData> {
120        let mut out_value: duckdb_value = std::ptr::null_mut();
121        // SAFETY: self.raw and context.as_raw() are valid; out_value is a valid
122        // out-pointer that DuckDB writes an owned duckdb_value into.
123        let err_raw =
124            unsafe { duckdb_expression_fold(context.as_raw(), self.raw, &raw mut out_value) };
125        // SAFETY: duckdb_expression_fold returns an owned duckdb_error_data.
126        let err = unsafe { ErrorData::from_raw(err_raw) };
127        if err.has_error() {
128            // SAFETY: out_value may have been left null/invalid; destroy any value.
129            if !out_value.is_null() {
130                drop(unsafe { Value::from_raw(out_value) });
131            }
132            return Err(err);
133        }
134        // SAFETY: folding succeeded, so out_value is an owned duckdb_value.
135        Ok(unsafe { Value::from_raw(out_value) })
136    }
137}
138
139impl Drop for Expression {
140    fn drop(&mut self) {
141        if !self.raw.is_null() {
142            // SAFETY: self.raw is a valid duckdb_expression that we own.
143            unsafe { duckdb_destroy_expression(&raw mut self.raw) };
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn null_expression_is_null() {
154        let expr = unsafe { Expression::from_raw(std::ptr::null_mut()) };
155        assert!(expr.is_null());
156        assert!(!expr.is_foldable());
157        assert!(expr.return_type().is_none());
158    }
159
160    #[test]
161    fn size_of_expression_is_one_pointer() {
162        assert_eq!(
163            std::mem::size_of::<Expression>(),
164            std::mem::size_of::<usize>()
165        );
166    }
167}