Skip to content

Commit 952943a

Browse files
authored
Handle invalid & unsupported DuckDB types more gracefully (#629)
refs #627 incorporates #630
2 parents 3d54365 + 78e50c4 commit 952943a

File tree

2 files changed

+124
-42
lines changed

2 files changed

+124
-42
lines changed

crates/duckdb/src/core/logical_type.rs

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ use crate::ffi::*;
99
/// <https://duckdb.org/docs/api/c/types>
1010
#[repr(u32)]
1111
#[derive(Debug, PartialEq, Eq)]
12+
#[non_exhaustive]
1213
pub enum LogicalTypeId {
14+
/// Invalid
15+
Invalid = DUCKDB_TYPE_DUCKDB_TYPE_INVALID,
1316
/// Boolean
1417
Boolean = DUCKDB_TYPE_DUCKDB_TYPE_BOOLEAN,
1518
/// Tinyint
@@ -66,14 +69,37 @@ pub enum LogicalTypeId {
6669
Uuid = DUCKDB_TYPE_DUCKDB_TYPE_UUID,
6770
/// Union
6871
Union = DUCKDB_TYPE_DUCKDB_TYPE_UNION,
72+
/// Bit
73+
Bit = DUCKDB_TYPE_DUCKDB_TYPE_BIT,
74+
/// Time TZ
75+
TimeTZ = DUCKDB_TYPE_DUCKDB_TYPE_TIME_TZ,
6976
/// Timestamp TZ
7077
TimestampTZ = DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP_TZ,
78+
/// Unsigned Hugeint
79+
UHugeint = DUCKDB_TYPE_DUCKDB_TYPE_UHUGEINT,
80+
/// Array
81+
Array = DUCKDB_TYPE_DUCKDB_TYPE_ARRAY,
82+
/// Any
83+
Any = DUCKDB_TYPE_DUCKDB_TYPE_ANY,
84+
/// Bignum
85+
Bignum = DUCKDB_TYPE_DUCKDB_TYPE_BIGNUM,
86+
/// SqlNull
87+
SqlNull = DUCKDB_TYPE_DUCKDB_TYPE_SQLNULL,
88+
/// String Literal
89+
StringLiteral = DUCKDB_TYPE_DUCKDB_TYPE_STRING_LITERAL,
90+
/// Integer Literal
91+
IntegerLiteral = DUCKDB_TYPE_DUCKDB_TYPE_INTEGER_LITERAL,
92+
/// Time NS
93+
TimeNs = DUCKDB_TYPE_DUCKDB_TYPE_TIME_NS,
94+
/// DuckDB returned a type that this wrapper does not yet recognize
95+
Unsupported = u32::MAX,
7196
}
7297

7398
impl From<u32> for LogicalTypeId {
7499
/// Convert from u32 to LogicalTypeId
75100
fn from(value: u32) -> Self {
76101
match value {
102+
DUCKDB_TYPE_DUCKDB_TYPE_INVALID => Self::Invalid,
77103
DUCKDB_TYPE_DUCKDB_TYPE_BOOLEAN => Self::Boolean,
78104
DUCKDB_TYPE_DUCKDB_TYPE_TINYINT => Self::Tinyint,
79105
DUCKDB_TYPE_DUCKDB_TYPE_SMALLINT => Self::Smallint,
@@ -102,8 +128,19 @@ impl From<u32> for LogicalTypeId {
102128
DUCKDB_TYPE_DUCKDB_TYPE_MAP => Self::Map,
103129
DUCKDB_TYPE_DUCKDB_TYPE_UUID => Self::Uuid,
104130
DUCKDB_TYPE_DUCKDB_TYPE_UNION => Self::Union,
131+
DUCKDB_TYPE_DUCKDB_TYPE_BIT => Self::Bit,
132+
DUCKDB_TYPE_DUCKDB_TYPE_TIME_TZ => Self::TimeTZ,
105133
DUCKDB_TYPE_DUCKDB_TYPE_TIMESTAMP_TZ => Self::TimestampTZ,
106-
_ => panic!(),
134+
DUCKDB_TYPE_DUCKDB_TYPE_UHUGEINT => Self::UHugeint,
135+
DUCKDB_TYPE_DUCKDB_TYPE_ARRAY => Self::Array,
136+
DUCKDB_TYPE_DUCKDB_TYPE_ANY => Self::Any,
137+
DUCKDB_TYPE_DUCKDB_TYPE_BIGNUM => Self::Bignum,
138+
DUCKDB_TYPE_DUCKDB_TYPE_SQLNULL => Self::SqlNull,
139+
DUCKDB_TYPE_DUCKDB_TYPE_STRING_LITERAL => Self::StringLiteral,
140+
DUCKDB_TYPE_DUCKDB_TYPE_INTEGER_LITERAL => Self::IntegerLiteral,
141+
DUCKDB_TYPE_DUCKDB_TYPE_TIME_NS => Self::TimeNs,
142+
// Unknown / forward compatible types
143+
_ => Self::Unsupported,
107144
}
108145
}
109146
}
@@ -119,6 +156,8 @@ impl Debug for LogicalTypeHandle {
119156
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
120157
let id = self.id();
121158
match id {
159+
LogicalTypeId::Invalid => write!(f, "Invalid"),
160+
LogicalTypeId::Unsupported => write!(f, "Unsupported({})", self.raw_id()),
122161
LogicalTypeId::Struct => {
123162
write!(f, "struct<")?;
124163
for i in 0..self.num_children() {
@@ -129,7 +168,7 @@ impl Debug for LogicalTypeHandle {
129168
}
130169
write!(f, ">")
131170
}
132-
_ => write!(f, "{:?}", self.id()),
171+
_ => write!(f, "{:?}", id),
133172
}
134173
}
135174
}
@@ -248,8 +287,25 @@ impl LogicalTypeHandle {
248287

249288
/// Logical type ID
250289
pub fn id(&self) -> LogicalTypeId {
251-
let duckdb_type_id = unsafe { duckdb_get_type_id(self.ptr) };
252-
duckdb_type_id.into()
290+
self.raw_id().into()
291+
}
292+
293+
/// Logical type ID, with forward-compatibility awareness.
294+
///
295+
/// Returns `Ok(LogicalTypeId)` for all known ids (including `Invalid`), and
296+
/// `Err(raw_id)` when DuckDB returns an id this wrapper does not yet
297+
/// recognize.
298+
pub fn try_id(&self) -> Result<LogicalTypeId, u32> {
299+
let raw = self.raw_id();
300+
match LogicalTypeId::from(raw) {
301+
LogicalTypeId::Unsupported => Err(raw),
302+
id => Ok(id),
303+
}
304+
}
305+
306+
/// Raw logical type id returned by DuckDB C API
307+
pub fn raw_id(&self) -> u32 {
308+
unsafe { duckdb_get_type_id(self.ptr) }
253309
}
254310

255311
/// Logical type children num
@@ -258,6 +314,7 @@ impl LogicalTypeHandle {
258314
LogicalTypeId::Struct => unsafe { duckdb_struct_type_child_count(self.ptr) as usize },
259315
LogicalTypeId::Union => unsafe { duckdb_union_type_member_count(self.ptr) as usize },
260316
LogicalTypeId::List => 1,
317+
LogicalTypeId::Array => 1,
261318
_ => 0,
262319
}
263320
}
@@ -270,6 +327,7 @@ impl LogicalTypeHandle {
270327
let child_name_ptr = match self.id() {
271328
LogicalTypeId::Struct => duckdb_struct_type_child_name(self.ptr, idx as u64),
272329
LogicalTypeId::Union => duckdb_union_type_member_name(self.ptr, idx as u64),
330+
LogicalTypeId::Unsupported => panic!("unsupported logical type {}", self.raw_id()),
273331
_ => panic!("not a struct or union"),
274332
};
275333
let c_str = CString::from_raw(child_name_ptr);
@@ -284,7 +342,9 @@ impl LogicalTypeHandle {
284342
match self.id() {
285343
LogicalTypeId::Struct => duckdb_struct_type_child_type(self.ptr, idx as u64),
286344
LogicalTypeId::Union => duckdb_union_type_member_type(self.ptr, idx as u64),
287-
_ => panic!("not a struct or union"),
345+
LogicalTypeId::Array => duckdb_array_type_child_type(self.ptr),
346+
LogicalTypeId::Unsupported => panic!("unsupported logical type {}", self.raw_id()),
347+
_ => panic!("not a struct, union, or array"),
288348
}
289349
};
290350
unsafe { Self::new(c_logical_type) }
@@ -319,26 +379,36 @@ mod test {
319379

320380
#[test]
321381
fn test_struct() {
322-
let fields = &[("hello", LogicalTypeHandle::from(crate::core::LogicalTypeId::Boolean))];
382+
let fields = &[("hello", LogicalTypeHandle::from(LogicalTypeId::Boolean))];
323383
let typ = LogicalTypeHandle::struct_type(fields);
324384

325385
assert_eq!(typ.num_children(), 1);
326386
assert_eq!(typ.child_name(0), "hello");
327-
assert_eq!(typ.child(0).id(), crate::core::LogicalTypeId::Boolean);
387+
assert_eq!(typ.child(0).id(), LogicalTypeId::Boolean);
388+
}
389+
390+
#[test]
391+
fn test_array() {
392+
let child = LogicalTypeHandle::from(LogicalTypeId::Integer);
393+
let array = LogicalTypeHandle::array(&child, 4);
394+
395+
assert_eq!(array.id(), LogicalTypeId::Array);
396+
assert_eq!(array.num_children(), 1);
397+
assert_eq!(array.child(0).id(), LogicalTypeId::Integer);
328398
}
329399

330400
#[test]
331401
fn test_decimal() {
332402
let typ = LogicalTypeHandle::decimal(10, 2);
333403

334-
assert_eq!(typ.id(), crate::core::LogicalTypeId::Decimal);
404+
assert_eq!(typ.id(), LogicalTypeId::Decimal);
335405
assert_eq!(typ.decimal_width(), 10);
336406
assert_eq!(typ.decimal_scale(), 2);
337407
}
338408

339409
#[test]
340410
fn test_decimal_methods() {
341-
let typ = LogicalTypeHandle::from(crate::core::LogicalTypeId::Varchar);
411+
let typ = LogicalTypeHandle::from(LogicalTypeId::Varchar);
342412

343413
assert_eq!(typ.decimal_width(), 0);
344414
assert_eq!(typ.decimal_scale(), 0);
@@ -360,4 +430,25 @@ mod test {
360430
assert_eq!(typ.child_name(1), "world");
361431
assert_eq!(typ.child(1).id(), LogicalTypeId::Integer);
362432
}
433+
434+
#[test]
435+
fn test_invalid_type() {
436+
use crate::ffi::{duckdb_create_logical_type, DUCKDB_TYPE_DUCKDB_TYPE_INVALID};
437+
438+
// Create an invalid logical type (what DuckDB returns in certain error cases)
439+
let invalid_type =
440+
unsafe { LogicalTypeHandle::new(duckdb_create_logical_type(DUCKDB_TYPE_DUCKDB_TYPE_INVALID)) };
441+
442+
assert_eq!(invalid_type.id(), LogicalTypeId::Invalid);
443+
assert_eq!(invalid_type.try_id().unwrap(), LogicalTypeId::Invalid);
444+
assert_eq!(invalid_type.raw_id(), DUCKDB_TYPE_DUCKDB_TYPE_INVALID);
445+
446+
let debug_str = format!("{invalid_type:?}");
447+
assert_eq!(debug_str, "Invalid");
448+
}
449+
450+
#[test]
451+
fn test_unknown_type() {
452+
assert_eq!(LogicalTypeId::from(999_999), LogicalTypeId::Unsupported);
453+
}
363454
}

crates/duckdb/src/vtab/arrow.rs

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ use arrow::{
2525
record_batch::RecordBatch,
2626
};
2727

28-
use libduckdb_sys::{
29-
duckdb_date, duckdb_hugeint, duckdb_interval, duckdb_string_t, duckdb_time, duckdb_timestamp, duckdb_vector,
30-
};
28+
use libduckdb_sys::{duckdb_date, duckdb_string_t, duckdb_time, duckdb_timestamp, duckdb_vector};
3129
use num::{cast::AsPrimitive, ToPrimitive};
3230

3331
/// A pointer to the Arrow record batch for the table function.
@@ -261,8 +259,11 @@ pub fn flat_vector_to_arrow_array(
261259
vector: &mut FlatVector,
262260
len: usize,
263261
) -> Result<Arc<dyn Array>, Box<dyn std::error::Error>> {
264-
let type_id = vector.logical_type().id();
262+
let raw_type_id = vector.logical_type().raw_id();
263+
let type_id = LogicalTypeId::from(raw_type_id);
265264
match type_id {
265+
LogicalTypeId::Invalid => Err("Cannot convert invalid logical type returned by DuckDB".into()),
266+
LogicalTypeId::Unsupported => Err(format!("Unsupported DuckDB logical type ID {raw_type_id}").into()),
266267
LogicalTypeId::Integer => {
267268
let data = vector.as_slice_with_len::<i32>(len);
268269

@@ -461,35 +462,25 @@ pub fn flat_vector_to_arrow_array(
461462

462463
Ok(Arc::new(structs))
463464
}
464-
LogicalTypeId::Struct => {
465-
todo!()
466-
}
467-
LogicalTypeId::Decimal => {
468-
todo!()
469-
}
470-
LogicalTypeId::Map => {
471-
todo!()
472-
}
473-
LogicalTypeId::List => {
474-
todo!()
475-
}
476-
LogicalTypeId::Union => {
477-
todo!()
478-
}
479-
LogicalTypeId::Interval => {
480-
let _data = vector.as_slice_with_len::<duckdb_interval>(len);
481-
todo!()
482-
}
483-
LogicalTypeId::Hugeint => {
484-
let _data = vector.as_slice_with_len::<duckdb_hugeint>(len);
485-
todo!()
486-
}
487-
LogicalTypeId::Enum => {
488-
todo!()
489-
}
490-
LogicalTypeId::Uuid => {
491-
todo!()
492-
}
465+
LogicalTypeId::Interval => todo!(),
466+
LogicalTypeId::Hugeint => todo!(),
467+
LogicalTypeId::Decimal => todo!(),
468+
LogicalTypeId::Enum => todo!(),
469+
LogicalTypeId::List => todo!(),
470+
LogicalTypeId::Struct => todo!(),
471+
LogicalTypeId::Map => todo!(),
472+
LogicalTypeId::Array => todo!(),
473+
LogicalTypeId::Uuid => todo!(),
474+
LogicalTypeId::Union => todo!(),
475+
LogicalTypeId::Bit => todo!(),
476+
LogicalTypeId::TimeTZ => todo!(),
477+
LogicalTypeId::UHugeint => todo!(),
478+
LogicalTypeId::Any => todo!(),
479+
LogicalTypeId::Bignum => todo!(),
480+
LogicalTypeId::SqlNull => todo!(),
481+
LogicalTypeId::StringLiteral => todo!(),
482+
LogicalTypeId::IntegerLiteral => todo!(),
483+
LogicalTypeId::TimeNs => todo!(),
493484
}
494485
}
495486

0 commit comments

Comments
 (0)