duckdb-extension-framework 0.4.0

Purely experimental DuckDB extension framework
Documentation
use crate::constants::DuckDBType;
use crate::database::Database;
use crate::duckly::{
    duckdb_bind_info, duckdb_data_chunk, duckdb_function_info, duckdb_init_info, duckdb_query,
};
use crate::duckly::{
    duckdb_destroy_result, duckdb_free, duckdb_result, duckdb_result_error,
    duckdb_state_DuckDBError, duckdb_value_varchar,
};
use crate::{
    malloc_struct, BindInfo, DataChunk, FunctionInfo, InitInfo, LogicalType, TableFunction,
};
use std::error::Error;
use std::ffi::{CStr, CString};
use std::mem;
use std::ptr::{null, null_mut};

#[repr(C)]
struct TestInitInfo {
    done: bool,
}

unsafe extern "C" fn func(info: duckdb_function_info, output: duckdb_data_chunk) {
    let info = FunctionInfo::from(info);
    let output = DataChunk::from(output);

    let init_info = info.get_init_data::<TestInitInfo>();

    if (*init_info).done {
        output.set_size(0);
    } else {
        (*init_info).done = true;

        let vector = output.get_vector(0);

        let string = CString::new("hello world").expect("unable to build string");
        vector.assign_string_element(0, string.as_ptr());

        output.set_size(1);
    }
}

unsafe extern "C" fn init(info: duckdb_init_info) {
    let info = InitInfo::from(info);

    let data = malloc_struct::<TestInitInfo>();

    (*data).done = false;

    info.set_init_data(data.cast(), Some(duckdb_free))
}

unsafe extern "C" fn bind(info: duckdb_bind_info) {
    let info = BindInfo::from(info);

    info.add_result_column("column0", LogicalType::new(DuckDBType::Varchar));

    let param = info.get_parameter(0).get_varchar();

    assert_eq!("hello.json", param.to_str().unwrap());
}

#[test]
fn test_database_creation() -> Result<(), Box<dyn Error>> {
    let db = Database::new()?;
    let conn = db.connect()?;

    let table_function = TableFunction::default();
    table_function
        .add_parameter(&LogicalType::new(DuckDBType::Json))
        .set_name("read_json")
        .supports_pushdown(false)
        .set_function(Some(func))
        .set_init(Some(init))
        .set_bind(Some(bind));
    conn.register_table_function(table_function)?;

    let query = CString::new("select * from read_json('hello.json')")?;

    unsafe {
        let mut result: duckdb_result = mem::zeroed();
        let connection = conn.get_ptr();

        assert_ne!(connection, null_mut());

        if duckdb_query(connection, query.as_ptr(), &mut result) == duckdb_state_DuckDBError {
            let error = duckdb_result_error(&mut result);
            assert_ne!(error, null());
            let error = CStr::from_ptr(error);

            panic!("error: {}", error.to_str().unwrap());
        }

        let ptr = duckdb_value_varchar(&mut result, 0, 0);
        assert_ne!(ptr, null_mut());
        let value = CStr::from_ptr(ptr);

        assert_eq!(value.to_str()?, "hello world");

        duckdb_free(ptr.cast());

        duckdb_destroy_result(&mut result);
    };

    drop(conn);

    drop(db);

    Ok(())
}