Last active
April 1, 2024 12:57
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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