Skip to main content

better_duck_core/raw/
client_context.rs

1//! RAII wrapper for `duckdb_client_context`.
2//!
3//! A client context is obtained from a connection (or from a table function's bind
4//! info) and exposes the stable connection id. The handle is owned — it must be
5//! destroyed with `duckdb_destroy_client_context` — but it is only meaningful while
6//! the connection (or bind call) it came from is alive. [`ClientContext`] is
7//! therefore **lifetime-bound** to that owner: the borrow it captures prevents it
8//! from outliving the connection at compile time, and `Drop` destroys the handle
9//! exactly once.
10// FFI pointer args are used safely inside `unsafe` blocks.
11#![allow(clippy::not_unsafe_ptr_arg_deref)]
12
13use std::marker::PhantomData;
14
15use crate::ffi::{
16    duckdb_client_context, duckdb_client_context_get_connection_id, duckdb_destroy_client_context,
17};
18
19/// An owned `duckdb_client_context`, borrow-tied to the owner it came from so it
20/// cannot outlive it.
21pub struct ClientContext<'owner> {
22    ctx: duckdb_client_context,
23    _owner: PhantomData<&'owner ()>,
24}
25
26impl<'owner> ClientContext<'owner> {
27    /// Wraps a raw client-context handle, or `None` if null.
28    ///
29    /// # Safety
30    ///
31    /// `ctx` must be a `duckdb_client_context` produced for an owner that outlives
32    /// `'owner` (a connection or a live bind call), and ownership of the handle is
33    /// transferred here (destroyed on drop).
34    pub(crate) unsafe fn from_raw(ctx: duckdb_client_context) -> Option<ClientContext<'owner>> {
35        if ctx.is_null() {
36            return None;
37        }
38        Some(ClientContext { ctx, _owner: PhantomData })
39    }
40
41    /// The stable connection id of the connection this context belongs to.
42    #[must_use]
43    pub fn connection_id(&self) -> u64 {
44        // SAFETY: `self.ctx` is a valid, non-null client context owned by `self`.
45        unsafe { duckdb_client_context_get_connection_id(self.ctx) as u64 }
46    }
47
48    /// The raw handle, borrowed for the duration of `&self` (e.g. for
49    /// `duckdb_expression_fold`). The caller must not destroy it.
50    #[cfg(feature = "udf")]
51    pub(crate) fn as_raw(&self) -> duckdb_client_context {
52        self.ctx
53    }
54}
55
56impl Drop for ClientContext<'_> {
57    fn drop(&mut self) {
58        if !self.ctx.is_null() {
59            // SAFETY: `self.ctx` is a valid, non-null client context created by one of
60            // the get-context functions and not yet destroyed; destroyed once here.
61            unsafe { duckdb_destroy_client_context(&mut self.ctx) };
62        }
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use crate::connection::Connection;
69
70    #[test]
71    fn connection_exposes_a_stable_client_context_id() {
72        let conn = Connection::open_in_memory().unwrap();
73        let id = conn.client_context().expect("client context").connection_id();
74        // The same connection reports the same id on a second fetch.
75        assert_eq!(conn.client_context().unwrap().connection_id(), id);
76    }
77
78    #[test]
79    fn distinct_connections_on_one_db_have_distinct_ids() {
80        let a = Connection::open_in_memory().unwrap();
81        let b = a.try_clone().expect("second connection on the same database");
82        let ida = a.client_context().unwrap().connection_id();
83        let idb = b.client_context().unwrap().connection_id();
84        assert_ne!(ida, idb, "each connection has its own id");
85    }
86}