quack_rs/
client_context.rs1use std::ffi::CStr;
19
20use libduckdb_sys::{
21 duckdb_client_context, duckdb_client_context_get_catalog,
22 duckdb_client_context_get_config_option, duckdb_client_context_get_connection_id,
23 duckdb_config_option_scope, duckdb_connection, duckdb_connection_get_client_context,
24 duckdb_destroy_client_context, duckdb_destroy_value, duckdb_get_varchar, duckdb_value,
25};
26
27use crate::catalog::Catalog;
28use crate::error::ExtensionError;
29
30pub struct ClientContext {
35 ctx: duckdb_client_context,
36}
37
38impl ClientContext {
39 pub unsafe fn from_connection(con: duckdb_connection) -> Result<Self, ExtensionError> {
49 let mut ctx: duckdb_client_context = core::ptr::null_mut();
50 unsafe { duckdb_connection_get_client_context(con, &raw mut ctx) };
52 if ctx.is_null() {
53 return Err(ExtensionError::new(
54 "failed to obtain client context from connection",
55 ));
56 }
57 Ok(Self { ctx })
58 }
59
60 pub const unsafe fn from_raw(ctx: duckdb_client_context) -> Self {
66 Self { ctx }
67 }
68
69 #[must_use]
71 pub const fn as_raw(&self) -> duckdb_client_context {
72 self.ctx
73 }
74
75 pub unsafe fn catalog(&self, name: &CStr) -> Option<Catalog> {
85 let catalog = unsafe { duckdb_client_context_get_catalog(self.ctx, name.as_ptr()) };
87 if catalog.is_null() {
88 None
89 } else {
90 Some(unsafe { Catalog::from_raw(catalog) })
92 }
93 }
94
95 pub fn config_option(&self, name: &CStr) -> Option<String> {
99 let mut scope: duckdb_config_option_scope = 0;
100 let val: duckdb_value = unsafe {
102 duckdb_client_context_get_config_option(self.ctx, name.as_ptr(), &raw mut scope)
103 };
104 if val.is_null() {
105 return None;
106 }
107 let c_str = unsafe { duckdb_get_varchar(val) };
109 let result = if c_str.is_null() {
110 None
111 } else {
112 unsafe { CStr::from_ptr(c_str) }
114 .to_str()
115 .ok()
116 .map(String::from)
117 };
118 if !c_str.is_null() {
120 unsafe {
121 libduckdb_sys::duckdb_free(c_str.cast::<core::ffi::c_void>());
122 }
123 }
124 let mut val_mut = val;
126 unsafe {
127 duckdb_destroy_value(&raw mut val_mut);
128 }
129 result
130 }
131
132 #[must_use]
134 pub fn connection_id(&self) -> u64 {
135 unsafe { duckdb_client_context_get_connection_id(self.ctx) }
137 }
138}
139
140impl Drop for ClientContext {
141 fn drop(&mut self) {
142 unsafe {
144 duckdb_destroy_client_context(&raw mut self.ctx);
145 }
146 }
147}
148
149#[cfg(all(test, feature = "bundled-test"))]
150mod tests {
151 use super::*;
152
153 fn open_raw_connection() -> (libduckdb_sys::duckdb_database, duckdb_connection) {
155 let _db = crate::testing::InMemoryDb::open().unwrap();
157
158 let mut db: libduckdb_sys::duckdb_database = core::ptr::null_mut();
159 let mut con: duckdb_connection = core::ptr::null_mut();
160
161 unsafe {
163 let rc = libduckdb_sys::duckdb_open(core::ptr::null(), &raw mut db);
164 assert_eq!(rc, libduckdb_sys::DuckDBSuccess, "duckdb_open failed");
165 let rc = libduckdb_sys::duckdb_connect(db, &raw mut con);
166 assert_eq!(rc, libduckdb_sys::DuckDBSuccess, "duckdb_connect failed");
167 }
168 (db, con)
169 }
170
171 unsafe fn close_raw_connection(
173 mut con: duckdb_connection,
174 mut db: libduckdb_sys::duckdb_database,
175 ) {
176 unsafe {
177 libduckdb_sys::duckdb_disconnect(&raw mut con);
178 libduckdb_sys::duckdb_close(&raw mut db);
179 }
180 }
181
182 #[test]
183 fn from_connection_succeeds() {
184 let (db, con) = open_raw_connection();
185
186 let ctx = unsafe { ClientContext::from_connection(con) };
188 assert!(
189 ctx.is_ok(),
190 "from_connection should succeed: {:?}",
191 ctx.err()
192 );
193
194 drop(ctx.unwrap());
195 unsafe { close_raw_connection(con, db) };
197 }
198
199 #[test]
200 fn connection_id_returns_nonzero() {
201 let (db, con) = open_raw_connection();
202
203 let ctx = unsafe { ClientContext::from_connection(con) }.unwrap();
205 let _id = ctx.connection_id();
208
209 drop(ctx);
210 unsafe { close_raw_connection(con, db) };
212 }
213
214 #[test]
215 fn config_option_returns_some_for_known_setting() {
216 let (db, con) = open_raw_connection();
217
218 let ctx = unsafe { ClientContext::from_connection(con) }.unwrap();
220
221 let threads = ctx.config_option(c"threads");
223 assert!(threads.is_some(), "'threads' config option should exist");
224 let val: usize = threads.unwrap().parse().expect("threads should be numeric");
226 assert!(val > 0, "threads should be > 0");
227
228 drop(ctx);
229 unsafe { close_raw_connection(con, db) };
231 }
232
233 #[test]
234 fn catalog_returns_some_for_default() {
235 let (db, con) = open_raw_connection();
236
237 unsafe {
240 let sql = c"BEGIN TRANSACTION";
241 libduckdb_sys::duckdb_query(con, sql.as_ptr(), core::ptr::null_mut());
242 }
243
244 let ctx = unsafe { ClientContext::from_connection(con) }.unwrap();
246
247 let catalog = unsafe { ctx.catalog(c"") };
250 drop(catalog);
253
254 drop(ctx);
255 unsafe {
258 let sql = c"ROLLBACK";
259 libduckdb_sys::duckdb_query(con, sql.as_ptr(), core::ptr::null_mut());
260 }
261 unsafe { close_raw_connection(con, db) };
263 }
264}