Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/duckdb/src/core/logical_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ impl Debug for LogicalTypeHandle {
}
write!(f, ">")
}
LogicalTypeId::List => write!(f, "List<{:?}>", self.child(0)),
_ => write!(f, "{:?}", id),
}
}
Expand Down Expand Up @@ -340,6 +341,7 @@ impl LogicalTypeHandle {
pub fn child(&self, idx: usize) -> Self {
let c_logical_type = unsafe {
match self.id() {
LogicalTypeId::List => duckdb_list_type_child_type(self.ptr),
LogicalTypeId::Struct => duckdb_struct_type_child_type(self.ptr, idx as u64),
LogicalTypeId::Union => duckdb_union_type_member_type(self.ptr, idx as u64),
LogicalTypeId::Array => duckdb_array_type_child_type(self.ptr),
Expand Down
2 changes: 2 additions & 0 deletions crates/duckdb/src/core/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod data_chunk;
mod logical_type;
mod value;
mod vector;

pub use data_chunk::DataChunkHandle;
pub use logical_type::{LogicalTypeHandle, LogicalTypeId};
pub use value::ValueHandle;
pub use vector::*;
83 changes: 83 additions & 0 deletions crates/duckdb/src/core/value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use crate::{
ffi::*,
types::{ListType, ValueRef},
};

/// A wrapper around a DuckDB value handle.
#[derive(Debug)]
#[repr(C)]
pub struct ValueHandle {
pub(crate) ptr: duckdb_value,
// Do not add members so that array of this type can be used in FFI.
}

impl Drop for ValueHandle {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
duckdb_destroy_value(&mut self.ptr);
}
}
self.ptr = std::ptr::null_mut();
}
}

impl<'a> From<ValueRef<'a>> for ValueHandle {
fn from(value_ref: ValueRef<'a>) -> Self {
let ptr = match value_ref {
ValueRef::Null => unsafe { duckdb_create_null_value() },
ValueRef::Boolean(v) => unsafe { duckdb_create_bool(v) },
ValueRef::TinyInt(v) => unsafe { duckdb_create_int8(v) },
ValueRef::SmallInt(v) => unsafe { duckdb_create_int16(v) },
ValueRef::Int(v) => unsafe { duckdb_create_int32(v) },
ValueRef::BigInt(v) => unsafe { duckdb_create_int64(v) },
ValueRef::HugeInt(v) => {
let lower = v.cast_unsigned() as u64;
let upper = (v >> 64) as i64;
unsafe { duckdb_create_hugeint(duckdb_hugeint { lower, upper }) }
}
ValueRef::UTinyInt(v) => unsafe { duckdb_create_uint8(v) },
ValueRef::USmallInt(v) => unsafe { duckdb_create_uint16(v) },
ValueRef::UInt(v) => unsafe { duckdb_create_uint32(v) },
ValueRef::UBigInt(v) => unsafe { duckdb_create_uint64(v) },
ValueRef::Float(v) => unsafe { duckdb_create_float(v) },
ValueRef::Double(v) => unsafe { duckdb_create_double(v) },
ValueRef::Text(v) => unsafe { duckdb_create_varchar_length(v.as_ptr() as *const i8, v.len() as u64) },
ValueRef::Blob(v) => unsafe { duckdb_create_blob(v.as_ptr() as *const u8, v.len() as u64) },
ValueRef::List(ListType::Native(arr)) => {
let logical_type = value_ref
.data_type()
.inner_type()
.expect("List type doesn't have an inner type")
.logical_type_handle();
// Underlying DuckDB API isn't marked const unfortunately, so we have to use a mutable pointer.
let mut values = arr
.iter()
.map(ValueRef::from)
.map(ValueHandle::from)
.collect::<Vec<_>>();
let value_count = arr.len() as u64;
unsafe {
duckdb_create_list_value(
logical_type.ptr,
values[..].as_mut_ptr() as *mut duckdb_value,
value_count,
)
}
}
ValueRef::Timestamp(..)
| ValueRef::List(..)
| ValueRef::Date32(..)
| ValueRef::Time64(..)
| ValueRef::Interval { .. }
| ValueRef::Decimal(..)
| ValueRef::Enum(..)
| ValueRef::Struct(..)
| ValueRef::Array(..)
| ValueRef::Map(..)
| ValueRef::Union(..) => unimplemented!("Not implemented for {:?}", value_ref),
};
assert!(!ptr.is_null(), "Failed to create DuckDB value for {:?}", value_ref);
Self { ptr }
}
}
4 changes: 2 additions & 2 deletions crates/duckdb/src/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -621,12 +621,12 @@ impl<'stmt> Row<'stmt> {
DataType::LargeList(..) => {
let arr = column.as_any().downcast_ref::<array::LargeListArray>().unwrap();

ValueRef::List(ListType::Large(arr), row)
ValueRef::List(ListType::Large(arr, row))
}
DataType::List(..) => {
let arr = column.as_any().downcast_ref::<ListArray>().unwrap();

ValueRef::List(ListType::Regular(arr), row)
ValueRef::List(ListType::Regular(arr, row))
}
DataType::Dictionary(key_type, ..) => {
let column = column.as_any();
Expand Down
7 changes: 6 additions & 1 deletion crates/duckdb/src/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ use super::{ffi, AndThenRows, Connection, Error, MappedRows, Params, RawStatemen
use crate::{arrow2, polars_dataframe::Polars};
use crate::{
arrow_batch::{Arrow, ArrowStream},
core::ValueHandle,
error::result_from_duckdb_prepare,
types::{TimeUnit, ToSql, ToSqlOutput},
types::{ListType, TimeUnit, ToSql, ToSqlOutput},
};

/// A prepared statement.
Expand Down Expand Up @@ -608,6 +609,10 @@ impl Statement<'_> {
let micros = nanos / 1_000;
ffi::duckdb_bind_interval(ptr, col as u64, ffi::duckdb_interval { months, days, micros })
},
ValueRef::List(ListType::Native(_)) => {
let value = ValueHandle::from(value);
unsafe { ffi::duckdb_bind_value(ptr, col as u64, value.ptr) }
}
_ => unreachable!("not supported: {}", value.data_type()),
};
result_from_duckdb_prepare(rc, ptr)
Expand Down
76 changes: 76 additions & 0 deletions crates/duckdb/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
//! implements [`ToSql`] or [`FromSql`] for the cases where you want to know if
//! a value was NULL (which gets translated to `None`).

use crate::core::{LogicalTypeHandle, LogicalTypeId};

pub use self::{
from_sql::{FromSql, FromSqlError, FromSqlResult},
ordered_map::OrderedMap,
Expand Down Expand Up @@ -194,6 +196,80 @@ impl fmt::Display for Type {
}
}

impl Type {
/// Returns the inner type of a list, if this type is a list.
pub fn inner_type(&self) -> Option<&Type> {
match self {
Type::List(inner_type) => Some(inner_type),
_ => None,
}
}

/// Returns the logical type ID for this type.
pub fn logical_type_id(&self) -> LogicalTypeId {
match self {
Type::Null => LogicalTypeId::SqlNull,
Type::Boolean => LogicalTypeId::Boolean,
Type::TinyInt => LogicalTypeId::Tinyint,
Type::SmallInt => LogicalTypeId::Smallint,
Type::Int => LogicalTypeId::Integer,
Type::BigInt => LogicalTypeId::Bigint,
Type::HugeInt => LogicalTypeId::Hugeint,
Type::UTinyInt => LogicalTypeId::UTinyint,
Type::USmallInt => LogicalTypeId::USmallint,
Type::UInt => LogicalTypeId::UInteger,
Type::UBigInt => LogicalTypeId::UBigint,
Type::Float => LogicalTypeId::Float,
Type::Double => LogicalTypeId::Double,
Type::Timestamp => LogicalTypeId::Timestamp,
Type::Text => LogicalTypeId::Varchar,
Type::Blob => LogicalTypeId::Blob,
Type::Date32 => LogicalTypeId::Date,
Type::Time64 => LogicalTypeId::Time,
Type::Interval => LogicalTypeId::Interval,
// Complex types
Type::Decimal => LogicalTypeId::Decimal,
Type::Enum => LogicalTypeId::Enum,
Type::List(_) => LogicalTypeId::List,
Type::Struct(_) => LogicalTypeId::Struct,
Type::Map(_, _) => LogicalTypeId::Map,
Type::Array(_, _) => LogicalTypeId::Array,
Type::Union => LogicalTypeId::Union,
Type::Any => LogicalTypeId::Any,
}
}

/// Returns the logical type handle for this type.
pub fn logical_type_handle(&self) -> LogicalTypeHandle {
match self {
Type::Null
| Type::Boolean
| Type::TinyInt
| Type::SmallInt
| Type::Int
| Type::BigInt
| Type::HugeInt
| Type::UTinyInt
| Type::USmallInt
| Type::UInt
| Type::UBigInt
| Type::Float
| Type::Double
| Type::Timestamp
| Type::Text
| Type::Blob
| Type::Date32
| Type::Time64
| Type::Interval
| Type::Any => self.logical_type_id().into(),
Type::List(inner_type) => LogicalTypeHandle::list(&inner_type.logical_type_handle()),
Type::Decimal | Type::Enum | Type::Struct(_) | Type::Map(_, _) | Type::Array(_, _) | Type::Union => {
unimplemented!("Logical type handle conversion not implemented for {:?}", self)
}
}
}
}

#[cfg(test)]
mod test {
use super::Value;
Expand Down
79 changes: 79 additions & 0 deletions crates/duckdb/src/types/to_sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,4 +361,83 @@ mod test {
assert_eq!(found_label, "target");
Ok(())
}

#[test]
fn test_list() -> crate::Result<()> {
use crate::{
params,
types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, Value, ValueRef},
Connection,
};

#[derive(Debug, PartialEq, Eq)]
struct MyList(Vec<i32>);

impl ToSql for MyList {
fn to_sql(&self) -> crate::Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::Owned(Value::List(
self.0.iter().map(|&x| Value::Int(x)).collect(),
)))
}
}

impl FromSql for MyList {
fn column_result(value_ref: ValueRef<'_>) -> FromSqlResult<Self> {
match value_ref.to_owned() {
Value::List(values) => values
.into_iter()
.map(|v| {
if let Value::Int(i) = v {
Ok(i)
} else {
Err(FromSqlError::InvalidType)
}
})
.collect::<Result<Vec<i32>, _>>()
.map(MyList),
_ => return FromSqlResult::Err(FromSqlError::InvalidType),
}
}
}

let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE foo (numbers INT[]);")?;

let list = MyList(vec![1, 2, 3, 4, 5]);
db.execute("INSERT INTO foo (numbers) VALUES (?)", params![&list])?;

let found_numbers: MyList = db.prepare("SELECT numbers FROM foo")?.query_one([], |r| r.get(0))?;
assert_eq!(found_numbers, MyList(vec![1, 2, 3, 4, 5]));

Ok(())
}

#[test]
#[should_panic = "Failed to create DuckDB value for List(Native([]))"]
fn test_empty_list() -> () {
use crate::{
params,
types::{ToSqlOutput, Value},
Connection,
};

#[derive(Debug, PartialEq, Eq)]
struct MyList(Vec<i32>);

impl ToSql for MyList {
fn to_sql(&self) -> crate::Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::Owned(Value::List(
self.0.iter().map(|&x| Value::Int(x)).collect(),
)))
}
}

let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo (numbers INT[]);").unwrap();

let list = MyList(vec![]);

// This should panic because the list is empty and DuckDB cannot determine the type of the list.
_ = db.execute("INSERT INTO foo (numbers) VALUES (?)", params![&list]);
}
}
5 changes: 4 additions & 1 deletion crates/duckdb/src/types/value.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::types::{value_ref::ListType, ValueRef};

use super::{Null, OrderedMap, TimeUnit, Type};
use rust_decimal::prelude::*;

Expand Down Expand Up @@ -238,7 +240,8 @@ impl Value {
Self::Date32(_) => Type::Date32,
Self::Time64(..) => Type::Time64,
Self::Interval { .. } => Type::Interval,
Self::Union(..) | Self::Struct(..) | Self::List(..) | Self::Array(..) | Self::Map(..) => todo!(),
Self::List(ref arr) => ValueRef::List(ListType::Native(arr)).data_type(),
Self::Union(..) | Self::Struct(..) | Self::Array(..) | Self::Map(..) => todo!(),
Self::Enum(..) => Type::Enum,
}
}
Expand Down
Loading