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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ path = "src/wasm_lib.rs"
crate-type = ["staticlib"]

[dependencies]
duckdb = { version = "1.4.1", features = ["vtab-loadable"] }
duckdb = { version = "1.4.1", features = [
"vscalar",
"vscalar-arrow",
"vtab-arrow",
"vtab-loadable"
] }
duckdb-loadable-macros = "0.1.11"
libduckdb-sys = { version = "1.4.1", features = ["loadable-extension"] }
69 changes: 67 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,77 @@ extern crate libduckdb_sys;

use duckdb::{
core::{DataChunkHandle, Inserter, LogicalTypeHandle, LogicalTypeId},
types::DuckString,
vscalar::{ScalarFunctionSignature, VScalar},
vtab::arrow::WritableVector,
vtab::{BindInfo, InitInfo, TableFunctionInfo, VTab},
Connection, Result,
};
use duckdb_loadable_macros::duckdb_entrypoint_c_api;
use libduckdb_sys as ffi;
use libduckdb_sys::duckdb_string_t;
use std::{
error::Error,
ffi::CString,
sync::atomic::{AtomicBool, Ordering},
};

struct EchoState {
multiplier: usize,
separator: String,
prefix: String,
}

impl Default for EchoState {
fn default() -> Self {
Self {
multiplier: 3,
separator: "📢".to_string(),
prefix: "🐤".to_string(),
}
}
}

struct EchoScalar {}

impl VScalar for EchoScalar {
type State = EchoState;

unsafe fn invoke(
state: &Self::State,
input: &mut DataChunkHandle,
output: &mut dyn WritableVector,
) -> Result<(), Box<dyn std::error::Error>> {
let values = input.flat_vector(0);
let values = values.as_slice_with_len::<duckdb_string_t>(input.len());
let strings = values
.iter()
.map(|ptr| DuckString::new(&mut { *ptr }).as_str().to_string())
.take(input.len());
let output = output.flat_vector();

for (i, s) in strings.enumerate() {
let res = format!(
"{} {}",
state.prefix,
std::iter::repeat(s)
.take(state.multiplier)
.collect::<Vec<_>>()
.join(&state.separator)
);
output.insert(i, res.as_str());
}
Ok(())
}

fn signatures() -> Vec<ScalarFunctionSignature> {
vec![ScalarFunctionSignature::exact(
vec![LogicalTypeId::Varchar.into()],
LogicalTypeId::Varchar.into(),
)]
}
}

#[repr(C)]
struct HelloBindData {
name: String,
Expand Down Expand Up @@ -43,7 +103,10 @@ impl VTab for HelloVTab {
})
}

fn func(func: &TableFunctionInfo<Self>, output: &mut DataChunkHandle) -> Result<(), Box<dyn std::error::Error>> {
fn func(
func: &TableFunctionInfo<Self>,
output: &mut DataChunkHandle,
) -> Result<(), Box<dyn std::error::Error>> {
let init_data = func.get_init_data();
let bind_data = func.get_bind_data();
if init_data.done.swap(true, Ordering::Relaxed) {
Expand All @@ -68,5 +131,7 @@ const EXTENSION_NAME: &str = env!("CARGO_PKG_NAME");
pub unsafe fn extension_entrypoint(con: Connection) -> Result<(), Box<dyn Error>> {
con.register_table_function::<HelloVTab>(EXTENSION_NAME)
.expect("Failed to register hello table function");
con.register_scalar_function::<EchoScalar>("rusty_echo")
.expect("Failed to register echo scala function");
Ok(())
}
}
22 changes: 22 additions & 0 deletions test/sql/rusty_echo.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# name: test/sql/rusty_echo.test
# description: test rusty_quack extension
# group: [quack]

# Before we load the extension, this will fail
statement error
SELECT rusty_echo('col0') FROM values ('Hello') as v;
----
Catalog Error: Scalar Function with name rusty_echo does not exist!

# Require statement will ensure the extension is loaded from now on
require rusty_quack

require icu

# Confirm the extension works
query I
SELECT rusty_echo(col0) FROM values ('Hello'), ('rusty'), ('world') as v;
----
🐤 Hello📢Hello📢Hello
🐤 rusty📢rusty📢rusty
🐤 world📢world📢world
Loading