Skip to content
Query.Farm
Talk with Us

Vector Gateway Interface for Rust

Extend DuckDB with functions written in Rust — backed by Apache Arrow, callable straight from SQL.

The vgi crate implements the Vector Gateway Interface (VGI) for Rust: you write a small worker exposing typed functions, and DuckDB calls them as if they were native. Data moves as Apache Arrow record batches on arrow-rs, so it stays columnar across the boundary — no row-by-row marshalling.

No C++ extension to compile, no linking against DuckDB, no version coupling. cargo build, ship the binary.

cargo add vgi arrow-array arrow-schema

Requires Rust 1.97+ and arrow-rs 59. A worker is an ordinary binary crate:

use vgi::catalog::CatalogModel;
use vgi::function::{ArgSpec, FunctionMetadata, ProcessParams, ScalarFunction};
use vgi::{Result, RpcError};

struct Double;

impl ScalarFunction for Double {
  fn name(&self) -> &str { "double" }
  fn metadata(&self) -> FunctionMetadata { /* … */ }
  fn argument_specs(&self) -> Vec<ArgSpec> {
      vec![ArgSpec::column("n", 0, "int64", "Value to double")]
  }
  fn process(&self, params: &ProcessParams, batch: &RecordBatch) -> Result<RecordBatch> {
      /* … one output value per input row … */
  }
}

fn main() {
  let mut worker = vgi::Worker::new();
  worker.register_scalar(Double);
  worker.set_catalog(CatalogModel { name: "calc".to_string(), ..Default::default() });
  worker.run();
}
ATTACH 'calc' (TYPE vgi, LOCATION './target/release/calcscalar');
SELECT calc.double(21);   -- 42
  • A trait is the function. No macro, no derive, no registry attribute — implement ScalarFunction (or one of its four siblings) and hand the value to Worker::register_*.
  • Shared function, per-scan producer. A TableFunction is Send + Sync and immutable; the cursor that walks a scan is a separate TableProducer built per execution. That split is why the function itself never needs a lock.
  • It runs where you need it. The same functions serve stdio, a Unix socket, TCP or HTTP — and compiled to wasm32, the whole worker runs inside the browser page next to a DuckDB-WASM engine. See Transports & runtimes.

Coming from another SDK? The protocol is identical and a Rust worker is wire-compatible with a Python, Go or TypeScript one — the same DuckDB extension drives all of them, so the concepts carry over unchanged.