1#![allow(clippy::not_unsafe_ptr_arg_deref)]
14
15use std::ffi::{c_void, CStr};
16
17use crate::{
18 error::{DuckDBConversionError, Result},
19 ffi::{
20 duckdb_destroy_value, duckdb_free, duckdb_get_date, duckdb_get_hugeint,
21 duckdb_get_interval, duckdb_get_struct_child, duckdb_get_time, duckdb_get_time_ns,
22 duckdb_get_time_tz, duckdb_get_timestamp, duckdb_get_timestamp_ms, duckdb_get_timestamp_ns,
23 duckdb_get_timestamp_s, duckdb_get_timestamp_tz, duckdb_get_type_id, duckdb_get_uhugeint,
24 duckdb_get_uuid, duckdb_get_value_type, duckdb_is_null_value, duckdb_type, duckdb_value,
25 duckdb_value_to_string, idx_t, DUCKDB_TYPE_DUCKDB_TYPE_DATE,
26 DUCKDB_TYPE_DUCKDB_TYPE_HUGEINT, DUCKDB_TYPE_DUCKDB_TYPE_INTERVAL,
27 DUCKDB_TYPE_DUCKDB_TYPE_TIME, DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP,
28 DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP_MS, DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP_NS,
29 DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP_S, DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP_TZ,
30 DUCKDB_TYPE_DUCKDB_TYPE_TIME_NS, DUCKDB_TYPE_DUCKDB_TYPE_TIME_TZ,
31 DUCKDB_TYPE_DUCKDB_TYPE_UHUGEINT, DUCKDB_TYPE_DUCKDB_TYPE_UUID,
32 },
33 types::numeric::{i128_from_hugeint, u128_from_uhugeint},
34 types::uuid::DuckUuid,
35};
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub struct IntervalParts {
40 pub months: i32,
42 pub days: i32,
44 pub micros: i64,
46}
47
48pub struct OwnedValue {
50 value: duckdb_value,
51}
52
53impl OwnedValue {
54 pub(crate) unsafe fn from_raw(value: duckdb_value) -> Option<OwnedValue> {
61 if value.is_null() {
62 return None;
63 }
64 Some(OwnedValue { value })
65 }
66
67 #[must_use]
69 pub fn is_null(&self) -> bool {
70 unsafe { duckdb_is_null_value(self.value) }
72 }
73
74 #[allow(dead_code)]
77 pub(crate) fn raw(&self) -> duckdb_value {
78 self.value
79 }
80
81 #[must_use]
84 pub fn to_sql_string(&self) -> Option<String> {
85 let ptr = unsafe { duckdb_value_to_string(self.value) };
88 if ptr.is_null() {
89 return None;
90 }
91 let s = unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned();
93 unsafe { duckdb_free(ptr as *mut c_void) };
95 Some(s)
96 }
97
98 #[must_use]
101 pub fn struct_child(
102 &self,
103 index: u64,
104 ) -> Option<OwnedValue> {
105 unsafe { OwnedValue::from_raw(duckdb_get_struct_child(self.value, index as idx_t)) }
108 }
109
110 #[must_use]
112 fn type_id(&self) -> duckdb_type {
113 let lt = unsafe { duckdb_get_value_type(self.value) };
116 unsafe { duckdb_get_type_id(lt) }
118 }
119
120 fn expect_type(
122 &self,
123 expected: duckdb_type,
124 what: &str,
125 ) -> Result<(), DuckDBConversionError> {
126 let actual = self.type_id();
127 if actual == expected {
128 Ok(())
129 } else {
130 Err(DuckDBConversionError::ConversionError(format!(
131 "value is type id {actual}, not {what} (id {expected})"
132 )))
133 }
134 }
135
136 pub fn get_hugeint(&self) -> Result<i128, DuckDBConversionError> {
141 self.expect_type(DUCKDB_TYPE_DUCKDB_TYPE_HUGEINT, "HUGEINT")?;
142 Ok(i128_from_hugeint(unsafe { duckdb_get_hugeint(self.value) }))
144 }
145
146 pub fn get_uhugeint(&self) -> Result<u128, DuckDBConversionError> {
151 self.expect_type(DUCKDB_TYPE_DUCKDB_TYPE_UHUGEINT, "UHUGEINT")?;
152 Ok(u128_from_uhugeint(unsafe { duckdb_get_uhugeint(self.value) }))
154 }
155
156 pub fn get_date_days(&self) -> Result<i32, DuckDBConversionError> {
161 self.expect_type(DUCKDB_TYPE_DUCKDB_TYPE_DATE, "DATE")?;
162 Ok(unsafe { duckdb_get_date(self.value) }.days)
164 }
165
166 pub fn get_time_micros(&self) -> Result<i64, DuckDBConversionError> {
171 self.expect_type(DUCKDB_TYPE_DUCKDB_TYPE_TIME, "TIME")?;
172 Ok(unsafe { duckdb_get_time(self.value) }.micros)
174 }
175
176 pub fn get_time_ns_nanos(&self) -> Result<i64, DuckDBConversionError> {
181 self.expect_type(DUCKDB_TYPE_DUCKDB_TYPE_TIME_NS, "TIME_NS")?;
182 Ok(unsafe { duckdb_get_time_ns(self.value) }.nanos)
184 }
185
186 pub fn get_time_tz_bits(&self) -> Result<u64, DuckDBConversionError> {
192 self.expect_type(DUCKDB_TYPE_DUCKDB_TYPE_TIME_TZ, "TIME_TZ")?;
193 Ok(unsafe { duckdb_get_time_tz(self.value) }.bits)
195 }
196
197 pub fn get_timestamp_micros(&self) -> Result<i64, DuckDBConversionError> {
202 self.expect_type(DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP, "TIMESTAMP")?;
203 Ok(unsafe { duckdb_get_timestamp(self.value) }.micros)
205 }
206
207 pub fn get_timestamp_tz_micros(&self) -> Result<i64, DuckDBConversionError> {
212 self.expect_type(DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP_TZ, "TIMESTAMP_TZ")?;
213 Ok(unsafe { duckdb_get_timestamp_tz(self.value) }.micros)
215 }
216
217 pub fn get_timestamp_seconds(&self) -> Result<i64, DuckDBConversionError> {
222 self.expect_type(DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP_S, "TIMESTAMP_S")?;
223 Ok(unsafe { duckdb_get_timestamp_s(self.value) }.seconds)
225 }
226
227 pub fn get_timestamp_millis(&self) -> Result<i64, DuckDBConversionError> {
232 self.expect_type(DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP_MS, "TIMESTAMP_MS")?;
233 Ok(unsafe { duckdb_get_timestamp_ms(self.value) }.millis)
235 }
236
237 pub fn get_timestamp_nanos(&self) -> Result<i64, DuckDBConversionError> {
242 self.expect_type(DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP_NS, "TIMESTAMP_NS")?;
243 Ok(unsafe { duckdb_get_timestamp_ns(self.value) }.nanos)
245 }
246
247 pub fn get_interval(&self) -> Result<IntervalParts, DuckDBConversionError> {
252 self.expect_type(DUCKDB_TYPE_DUCKDB_TYPE_INTERVAL, "INTERVAL")?;
253 let iv = unsafe { duckdb_get_interval(self.value) };
255 Ok(IntervalParts { months: iv.months, days: iv.days, micros: iv.micros })
256 }
257
258 pub fn get_uuid(&self) -> Result<DuckUuid, DuckDBConversionError> {
263 self.expect_type(DUCKDB_TYPE_DUCKDB_TYPE_UUID, "UUID")?;
264 let raw = unsafe { duckdb_get_uuid(self.value) };
268 Ok(DuckUuid(((raw.upper as u128) << 64) | (raw.lower as u128)))
269 }
270}
271
272impl Drop for OwnedValue {
273 fn drop(&mut self) {
274 if !self.value.is_null() {
275 unsafe { duckdb_destroy_value(&mut self.value) };
278 }
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use crate::types::value::DuckValue;
285 use std::collections::HashMap;
286
287 #[test]
288 fn null_and_scalar_values_report_correctly() {
289 assert!(DuckValue::Null.to_owned_value().unwrap().is_null());
290 let int = DuckValue::Int(42).to_owned_value().unwrap();
291 assert!(!int.is_null());
292 assert_eq!(int.to_sql_string().as_deref(), Some("42"));
293 }
294
295 #[test]
296 fn text_value_renders_as_sql_string() {
297 let v = DuckValue::text("hi").to_owned_value().unwrap();
298 assert_eq!(v.to_sql_string().as_deref(), Some("'hi'"));
300 }
301
302 #[test]
303 fn struct_children_are_owned_and_introspectable() {
304 let s = DuckValue::Struct(HashMap::from([("n".to_owned(), DuckValue::Int(7))]));
306 let owned = s.to_owned_value().unwrap();
307 let child = owned.struct_child(0).expect("struct child 0");
308 assert!(!child.is_null());
309 assert_eq!(child.to_sql_string().as_deref(), Some("7"));
310 assert!(owned.struct_child(5).is_none());
312 }
313
314 #[test]
315 fn typed_scalar_getters_read_matching_values_and_reject_mismatches() {
316 use crate::types::uuid::DuckUuid;
317
318 assert_eq!(DuckValue::HugeInt(-170).to_owned_value().unwrap().get_hugeint().unwrap(), -170);
320 assert_eq!(DuckValue::UHugeInt(340).to_owned_value().unwrap().get_uhugeint().unwrap(), 340);
321
322 let uuid = DuckUuid(0x0123_4567_89ab_cdef_0123_4567_89ab_cdef);
324 assert_eq!(DuckValue::Uuid(uuid).to_owned_value().unwrap().get_uuid().unwrap(), uuid);
325
326 assert!(DuckValue::UHugeInt(1).to_owned_value().unwrap().get_hugeint().is_err());
328 assert!(DuckValue::Int(1).to_owned_value().unwrap().get_timestamp_micros().is_err());
330 }
331
332 #[cfg(feature = "chrono")]
333 #[test]
334 fn interval_getter_reads_parts() {
335 let iv = DuckValue::Interval(chrono::Duration::microseconds(2 * 86_400 * 1_000_000))
338 .to_owned_value()
339 .unwrap()
340 .get_interval()
341 .unwrap();
342 assert_eq!((iv.months, iv.days), (0, 0));
343 assert_eq!(iv.micros, 2 * 86_400 * 1_000_000);
344 }
345
346 #[cfg(feature = "chrono")]
347 #[test]
348 fn temporal_getters_read_each_resolution() {
349 use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
350
351 let date = DuckValue::Date(NaiveDate::from_ymd_opt(2024, 3, 15).unwrap());
353 let days = date.to_owned_value().unwrap().get_date_days().unwrap();
354 assert_eq!(
355 days,
356 (NaiveDate::from_ymd_opt(2024, 3, 15).unwrap()
357 - NaiveDate::from_ymd_opt(1970, 1, 1).unwrap())
358 .num_days() as i32
359 );
360
361 let time = DuckValue::Time(NaiveTime::from_hms_opt(1, 2, 3).unwrap());
363 assert_eq!(
364 time.to_owned_value().unwrap().get_time_micros().unwrap(),
365 (3600 + 2 * 60 + 3) * 1_000_000
366 );
367
368 let ts = NaiveDateTime::new(
370 NaiveDate::from_ymd_opt(1970, 1, 1).unwrap(),
371 NaiveTime::from_hms_opt(0, 0, 1).unwrap(),
372 );
373 assert_eq!(
374 DuckValue::Timestamp(ts).to_owned_value().unwrap().get_timestamp_micros().unwrap(),
375 1_000_000
376 );
377 }
378}