Skip to content

Instantly share code, notes, and snippets.

@dvas0004
Last active April 1, 2024 12:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dvas0004/84a8f4048dc60eb948a1a17d0ecb1d05 to your computer and use it in GitHub Desktop.
Save dvas0004/84a8f4048dc60eb948a1a17d0ecb1d05 to your computer and use it in GitHub Desktop.
Defining a virtual table / loadable extenstion in rust using rusqlite (https://blog.davidvassallo.me/?p=4083)
use std::{ffi::c_int, marker::PhantomData};
use rusqlite::ffi;
use rusqlite::{ffi::{sqlite3_vtab, sqlite3_vtab_cursor}, vtab::{Context, IndexInfo, VTab, VTabConnection, VTabCursor, Values}, Connection};
use std::os::raw::c_char;
use rusqlite::{to_sqlite_error, Result};
#[repr(C)]
struct Test {
/// Base class. Must be first
base: sqlite3_vtab,
}
#[derive(Default)]
#[repr(C)]
struct TestCursor<'vtab> {
/// Base class. Must be first
base: sqlite3_vtab_cursor,
/// The rowid
row_id: i64,
phantom: PhantomData<&'vtab Test>,
data: Vec<String>, // this is where we load and store our "external" data - see `open`
}
// Write implementation of VTab trait.
// Step 1(a) from https://docs.rs/rusqlite/latest/rusqlite/vtab/index.html
unsafe impl<'vtab> VTab<'vtab> for Test {
type Aux = ();
type Cursor = TestCursor<'vtab>;
fn connect(
_: &mut VTabConnection,
_aux: Option<&()>,
_args: &[&[u8]],
) -> Result<(String, Test), rusqlite::Error> {
let vtab = Test {
base: sqlite3_vtab::default()
};
// our vtab schema is defined here
Ok(("CREATE TABLE test(id INT, name TEXT)".to_owned(), vtab))
}
fn best_index(&self, info: &mut IndexInfo) -> Result<(), rusqlite::Error> {
info.set_estimated_cost(1.);
Ok(())
}
// this is where we do external calls (e.g. APIs, files)
// to populate our external data
fn open(&'vtab mut self) -> Result<TestCursor<'vtab>, rusqlite::Error> {
let mut test_cursor = TestCursor::default();
test_cursor.data = vec!["a".to_owned(), "b".to_owned(), "c".to_owned()];
Ok(test_cursor)
}
}
// Write implementation of VTabCursor trait.
// Step 1(b) from https://docs.rs/rusqlite/latest/rusqlite/vtab/index.html
unsafe impl VTabCursor for TestCursor<'_> {
fn filter(
&mut self,
_idx_num: c_int,
_idx_str: Option<&str>,
_args: &Values<'_>,
) -> Result<(), rusqlite::Error> {
Ok(())
}
// next - how do we get the next record?
fn next(&mut self) -> Result<(), rusqlite::Error> {
self.row_id += 1;
Ok(())
}
// EOF - when should we stop calling `next`?
fn eof(&self) -> bool {
self.row_id >= self.data.len().try_into().unwrap()
}
// descibe the mappings between columns (expressed as numbers) and our data
// stored in the cursor
fn column(&self, ctx: &mut Context, col_number: c_int) -> Result<(), rusqlite::Error> {
match col_number {
0 => ctx.set_result(&self.row_id),
1 => ctx.set_result(&self.data[self.row_id as usize]),
_ => Err(rusqlite::Error::InvalidColumnName("n/a".to_owned())),
}
}
fn rowid(&self) -> Result<i64, rusqlite::Error> {
Ok(self.row_id)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment