duckdb 1.10505.0

Ergonomic wrapper for DuckDB
Documentation
use std::ffi::CString;

use crate::core::{DataChunkHandle, LogicalTypeHandle, LogicalTypeId};
use crate::ffi::duckdb_vector_size;

use super::list::MAX_VECTOR_SIZE;
use super::*;

#[test]
fn test_insert_string_values() {
    let chunk = DataChunkHandle::new(&[LogicalTypeId::Varchar.into()]);
    let vector = chunk.flat_vector(0);
    chunk.set_len(3);

    vector.insert(0, "first");
    vector.insert(1, &String::from("second"));
    let cstring = CString::new("third").unwrap();
    vector.insert(2, cstring);
}

#[test]
fn test_insert_byte_values() {
    let chunk = DataChunkHandle::new(&[LogicalTypeId::Blob.into()]);
    let vector = chunk.flat_vector(0);
    chunk.set_len(2);

    vector.insert(0, b"hello world".as_slice());
    vector.insert(
        1,
        &vec![0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64],
    );
}

#[test]
fn test_list_vector_get_entry() {
    let list_type = LogicalTypeHandle::list(&LogicalTypeId::Integer.into());
    let chunk = DataChunkHandle::new(&[list_type]);
    chunk.set_len(3);

    let mut list_vector = chunk.list_vector(0);

    list_vector.set_entry(0, 0, 2);
    list_vector.set_entry(1, 2, 1);
    list_vector.set_entry(2, 3, 2);

    assert_eq!(list_vector.get_entry(0), (0, 2));
    assert_eq!(list_vector.get_entry(1), (2, 1));
    assert_eq!(list_vector.get_entry(2), (3, 2));
}

#[test]
#[should_panic(expected = "exceeds vector capacity")]
fn test_flat_vector_row_is_null_checks_capacity() {
    let chunk = DataChunkHandle::new(&[LogicalTypeId::Integer.into()]);
    let vector = chunk.flat_vector(0);

    vector.row_is_null(unsafe { duckdb_vector_size() });
}

#[test]
fn test_vector_try_row_is_null_rejects_row_beyond_capacity() {
    let row = unsafe { duckdb_vector_size() };
    let chunk = DataChunkHandle::new(&[
        LogicalTypeId::Integer.into(),
        LogicalTypeHandle::list(&LogicalTypeId::Integer.into()),
        LogicalTypeHandle::array(&LogicalTypeId::Integer.into(), 2),
        LogicalTypeHandle::struct_type(&[("value", LogicalTypeId::Integer.into())]),
    ]);

    assert!(chunk.flat_vector(0).try_row_is_null(row).is_err());
    assert!(chunk.list_vector(1).try_row_is_null(row).is_err());
    assert!(chunk.array_vector(2).try_row_is_null(row).is_err());
    assert!(chunk.struct_vector(3).try_row_is_null(row).is_err());
}

#[test]
fn test_struct_vector_typed_child_accessors_smoke() {
    let int_type = LogicalTypeHandle::from(LogicalTypeId::Integer);
    let list_type = LogicalTypeHandle::list(&int_type);
    let array_type = LogicalTypeHandle::array(&int_type, 2);
    let nested_struct_type =
        LogicalTypeHandle::struct_type(&[("value", LogicalTypeHandle::from(LogicalTypeId::Integer))]);
    let struct_type = LogicalTypeHandle::struct_type(&[
        ("items", list_type),
        ("pair", array_type),
        ("nested", nested_struct_type),
    ]);
    let chunk = DataChunkHandle::new(&[struct_type]);
    let vector = chunk.struct_vector(0);

    let list_child = vector.list_vector_child(0);
    assert!(list_child.is_empty());
    let array_child = vector.array_vector_child(1);
    assert_eq!(array_child.get_array_size(), 2);
    let struct_child = vector.struct_vector_child(2);
    assert_eq!(struct_child.num_children(), 1);
    assert_eq!(struct_child.child_name(0).to_str().unwrap(), "value");

    let flat_child = vector.child(1, 2);
    assert_eq!(flat_child.capacity(), 2);

    let child_count = unsafe { duckdb_vector_size() as usize } + 1;
    let struct_type = LogicalTypeHandle::struct_type(&[("value", LogicalTypeHandle::from(LogicalTypeId::Integer))]);
    let list_type = LogicalTypeHandle::list(&struct_type);
    let chunk = DataChunkHandle::new(&[list_type]);
    let list = chunk.list_vector(0);
    let child = list.struct_child(child_count);
    assert_eq!(child.num_children(), 1);
}

#[test]
#[should_panic(expected = "exceeds parent capacity")]
fn test_struct_vector_child_rejects_capacity_beyond_parent() {
    let struct_type = LogicalTypeHandle::struct_type(&[("value", LogicalTypeHandle::from(LogicalTypeId::Integer))]);
    let chunk = DataChunkHandle::new(&[struct_type]);
    let vector = chunk.struct_vector(0);
    let capacity = unsafe { duckdb_vector_size() as usize } + 1;

    vector.child(0, capacity);
}

#[test]
#[should_panic(expected = "exceeds backing capacity")]
fn test_array_vector_child_rejects_capacity_beyond_parent() {
    let array_type = LogicalTypeHandle::array(&LogicalTypeId::Integer.into(), 2);
    let chunk = DataChunkHandle::new(&[array_type]);
    let vector = chunk.array_vector(0);
    let capacity = (unsafe { duckdb_vector_size() as usize } + 1) * 2;

    vector.child(capacity);
}

#[test]
fn test_array_vector_set_child_uses_reserved_nested_capacity() -> Result<()> {
    let item_type = LogicalTypeHandle::array(&LogicalTypeId::Integer.into(), 2);
    let list_type = LogicalTypeHandle::list(&item_type);
    let chunk = DataChunkHandle::new(&[list_type]);
    let list = chunk.list_vector(0);
    let child_count = unsafe { duckdb_vector_size() as usize } + 1;
    list.try_reserve(child_count)?;

    let array = list.array_child();
    let data = (0..(child_count * 2) as i32).collect::<Vec<_>>();
    unsafe { array.set_child(&data) };
    let child = array.child(data.len());
    let output = unsafe { child.as_slice_with_len::<i32>(data.len()) };

    assert_eq!(output[data.len() - 1], data[data.len() - 1]);

    Ok(())
}

#[test]
fn test_list_vector_set_len_reserves_list_child_storage() {
    let child_type = LogicalTypeHandle::list(&LogicalTypeId::Integer.into());
    let list_type = LogicalTypeHandle::list(&child_type);
    let chunk = DataChunkHandle::new(&[list_type]);
    let list = chunk.list_vector(0);
    let child_count = unsafe { duckdb_vector_size() as usize } + 1;
    list.set_len(child_count);

    let mut child = list.list_child();
    child.set_entry(child_count - 1, 0, 0);

    assert_eq!(child.get_entry(child_count - 1), (0, 0));
    assert!(!child.row_is_null((child_count - 1) as u64));
}

#[test]
fn test_list_vector_set_len_reserves_array_child_storage() {
    let child_type = LogicalTypeHandle::array(&LogicalTypeId::Integer.into(), 2);
    let list_type = LogicalTypeHandle::list(&child_type);
    let chunk = DataChunkHandle::new(&[list_type]);
    let list = chunk.list_vector(0);
    let child_count = unsafe { duckdb_vector_size() as usize } + 1;
    list.set_len(child_count);

    let child = list.array_child();

    assert!(!child.row_is_null((child_count - 1) as u64));
}

#[test]
fn test_list_vector_try_get_entry_rejects_row_beyond_capacity() {
    let list_type = LogicalTypeHandle::list(&LogicalTypeId::Integer.into());
    let chunk = DataChunkHandle::new(&[list_type]);
    let list = chunk.list_vector(0);
    let row = unsafe { duckdb_vector_size() as usize };

    let err = list.try_get_entry(row).unwrap_err();

    assert_eq!(
        err.to_string(),
        format!("list entry row {row} exceeds vector capacity {row}")
    );
}

#[test]
#[cfg(target_pointer_width = "64")]
fn test_list_vector_try_set_len_rejects_oversized_capacity() {
    let list_type = LogicalTypeHandle::list(&LogicalTypeId::Integer.into());
    let chunk = DataChunkHandle::new(&[list_type]);
    let list = chunk.list_vector(0);
    let oversized = MAX_VECTOR_SIZE as usize + 1;

    let err = list.try_set_len(oversized).unwrap_err();

    assert_eq!(
        err.to_string(),
        format!(
            "cannot reserve {oversized} elements in DuckDB list vector: exceeds maximum vector size {MAX_VECTOR_SIZE}"
        )
    );
}

#[test]
fn test_list_vector_list_child_retains_default_capacity_before_len() {
    let child_type = LogicalTypeHandle::list(&LogicalTypeId::Integer.into());
    let list_type = LogicalTypeHandle::list(&child_type);
    let chunk = DataChunkHandle::new(&[list_type]);
    let list = chunk.list_vector(0);

    let mut child = list.list_child();
    child.set_entry(0, 0, 0);

    assert_eq!(child.get_entry(0), (0, 0));
}

#[test]
fn test_list_vector_array_child_retains_default_capacity_before_len() {
    let child_type = LogicalTypeHandle::array(&LogicalTypeId::Integer.into(), 2);
    let list_type = LogicalTypeHandle::list(&child_type);
    let chunk = DataChunkHandle::new(&[list_type]);
    let list = chunk.list_vector(0);

    let child = list.array_child();

    assert!(!child.row_is_null(0));
}