Add a placeholder rusqlite VTab impl.

This commit is contained in:
Sebastian Jeltsch 2026-01-28 14:33:17 +01:00
parent bfde60f0c4
commit bd7cf45c3b
4 changed files with 127 additions and 17 deletions

View file

@ -81,7 +81,7 @@ minijinja = { version = "2.1.2", default-features = false }
parking_lot = { version = "0.12.3", default-features = false, features = ["send_guard", "arc_lock"] }
rand = { version = "^0.9.0" }
reqwest = { version = "0.13.1", default-features = false, features = ["rustls", "json"] }
rusqlite = { version = "0.38.0", default-features = false, features = ["bundled", "cache", "column_decltype", "functions", "backup", "preupdate_hook"] }
rusqlite = { version = "0.38.0", default-features = false, features = ["array", "bundled", "cache", "column_decltype", "functions", "backup", "preupdate_hook", "vtab"] }
rust-embed = { version = "8.4.0", default-features = false, features = ["mime-guess"] }
serde = { version = "^1.0.203", features = ["derive"] }
serde_json = { version = "^1.0.117" }

View file

@ -75,7 +75,7 @@ interface init-endpoint {
}
record sqlite-extensions {
/// Functions returning a scalar as opposed to being "table-valued".
/// Functions returning a scalar as opposed to aggregate or "table-valued" functions.
scalar-functions: list<sqlite-scalar-function>,
/// Sqlite moules.
modules: list<sqlite-module>,

View file

@ -1,3 +1,8 @@
use rusqlite::vtab::{
Context, CreateVTab, Filters, IndexInfo, VTab, VTabConnection, VTabCursor, eponymous_only_module,
read_only_module,
};
use std::marker::PhantomData;
use std::sync::Arc;
use tokio::sync::Mutex;
use wasmtime::{Result, Store};
@ -12,8 +17,14 @@ pub struct SqliteScalarFunction {
}
#[derive(Clone)]
pub struct SqliteFunctions {
pub struct SqliteModule {
pub name: String,
}
#[derive(Clone)]
pub struct SqliteExtensions {
pub scalar_functions: Vec<SqliteScalarFunction>,
pub modules: Vec<SqliteModule>,
}
struct SqliteStoreInternal {
@ -38,28 +49,33 @@ impl SqliteStore {
}
// Call WASM components `init` implementation.
pub async fn initialize_sqlite_functions(
pub async fn initialize_sqlite_extensions(
&self,
args: crate::InitArgs,
) -> Result<SqliteFunctions, Error> {
) -> Result<SqliteExtensions, Error> {
use crate::host::exports::trailbase::component::init_endpoint::{
Arguments, SqliteExtensions as WitSqliteExtensions,
};
let api = self.state.bindings.trailbase_component_init_endpoint();
let args = crate::host::exports::trailbase::component::init_endpoint::Arguments {
let args = Arguments {
version: args.version,
};
let mut store = self.state.store.lock().await;
let functions = store
let WitSqliteExtensions {
modules,
scalar_functions,
} = store
.run_concurrent(async |accessor| -> Result<_, Error> {
let (functions, task_exit) = api.call_init_sqlite_extensions(accessor, args).await?;
let (extensions, task_exit) = api.call_init_sqlite_extensions(accessor, args).await?;
task_exit.block(accessor).await;
return Ok(functions);
return Ok(extensions);
})
.await??;
return Ok(SqliteFunctions {
scalar_functions: functions
.scalar_functions
return Ok(SqliteExtensions {
scalar_functions: scalar_functions
.into_iter()
.map(|f| {
return SqliteScalarFunction {
@ -71,6 +87,10 @@ impl SqliteStore {
};
})
.collect(),
modules: modules
.into_iter()
.map(|m| return SqliteModule { name: m.name })
.collect(),
});
}
@ -110,11 +130,11 @@ impl SqliteStore {
pub fn setup_connection(
conn: &rusqlite::Connection,
store: SqliteStore,
functions: &SqliteFunctions,
extensions: SqliteExtensions,
) -> Result<(), rusqlite::Error> {
use crate::host::exports::trailbase::component::sqlite_function_endpoint::Value;
for function in &functions.scalar_functions {
for function in &extensions.scalar_functions {
let store = store.clone();
let function_name = function.name.clone();
@ -160,5 +180,95 @@ pub fn setup_connection(
)?;
}
for module in extensions.modules {
// TODO: call runtime to create module resource and then pass resource to Module.
conn
.create_module(
module.name.as_str(),
// &eponymous_only_module::<WasmVTab>(),
&read_only_module::<WasmVTab>(),
None,
)
.expect("FIXME");
}
return Ok(());
}
// TODO: Should maybe implement UpdateVTab instead of just VTab. Should hold a WASM
// resource.
#[repr(C)]
struct WasmVTab {
/// Base class. Must be first
base: rusqlite::vtab::sqlite3_vtab,
}
#[allow(unsafe_code)]
unsafe impl<'vtab> VTab<'vtab> for WasmVTab {
type Aux = ();
type Cursor = WasmVTabCursor<'vtab>;
fn connect(
_: &mut VTabConnection,
_aux: Option<&()>,
_args: &[&[u8]],
) -> rusqlite::Result<(String, Self)> {
let vtab = Self {
base: rusqlite::ffi::sqlite3_vtab::default(),
};
return Ok(("CREATE TABLE x(value)".to_owned(), vtab));
}
fn best_index(&self, info: &mut IndexInfo) -> rusqlite::Result<()> {
info.set_estimated_cost(1.);
return Ok(());
}
fn open(&mut self) -> rusqlite::Result<WasmVTabCursor<'_>> {
Ok(WasmVTabCursor::default())
}
}
impl<'vtab> CreateVTab<'vtab> for WasmVTab {
const KIND: rusqlite::vtab::VTabKind = rusqlite::vtab::VTabKind::Default;
}
#[derive(Default)]
#[repr(C)]
struct WasmVTabCursor<'vtab> {
/// Base class. Must be first
base: rusqlite::ffi::sqlite3_vtab_cursor,
/// The rowid
row_id: i64,
phantom: PhantomData<&'vtab WasmVTab>,
}
unsafe impl VTabCursor for WasmVTabCursor<'_> {
fn filter(
&mut self,
_idx_num: std::ffi::c_int,
_idx_str: Option<&str>,
_args: &Filters<'_>,
) -> rusqlite::Result<()> {
self.row_id = 1;
return Ok(());
}
fn next(&mut self) -> rusqlite::Result<()> {
self.row_id += 1;
return Ok(());
}
fn eof(&self) -> bool {
return self.row_id > 1;
}
fn column(&self, ctx: &mut Context, i: std::ffi::c_int) -> rusqlite::Result<()> {
return ctx.set_result(&self.row_id);
}
fn rowid(&self) -> rusqlite::Result<i64> {
return Ok(self.row_id);
}
}

View file

@ -503,12 +503,12 @@ mod tests {
let store = functions::SqliteStore::new(&runtime).await.unwrap();
let functions = store
.initialize_sqlite_functions(InitArgs { version: None })
let extensions = store
.initialize_sqlite_extensions(InitArgs { version: None })
.await
.unwrap();
functions::setup_connection(conn, store, &functions).unwrap();
functions::setup_connection(conn, store, extensions).unwrap();
return runtime;
}