How to Extend Turso with Virtual Tables (vtab): Implementation Guide
Turso supports three virtual table mechanisms—pragma, external, and internal—that let developers extend queryable functionality without modifying the core storage engine.
Turso, the open-source edge database maintained by tursodatabase/turso, exposes a modular virtual table (vtab) subsystem for adding custom table-like objects. By implementing specific traits or dynamic library interfaces, you can expose system metadata, integrate external data sources, or create lightweight Rust-based tables that participate in SQL queries alongside regular tables.
Types of Virtual Tables in Turso
Turso categorizes virtual tables based on where the implementation lives and how it is loaded:
| Kind | Location | Typical Use |
|---|---|---|
| Pragma | Built-in tables exposing pragma information | Read-only system metadata |
| External | Dynamic library loaded at runtime | Custom extensions (CSV, regexp, crypto) |
| Internal | Rust structs implementing InternalVirtualTable |
Lightweight tables exposing internal state |
The core type unifying all implementations is VirtualTable in [core/vtab.rs](https://github.com/tursodatabase/turso/blob/main/core/vtab.rs#L19-L25). It stores the table name, column metadata, a VTabKind discriminant, and a VirtualTableType that determines cursor construction during query execution.
Implementing Internal Virtual Tables in Rust
Internal virtual tables are pure-Rust objects that implement the InternalVirtualTable trait defined in core/vtab.rs. This approach requires no external shared libraries and keeps the extension code compiled directly into your Turso application.
The InternalVirtualTable Trait
To create an internal table, implement the following interface:
fn name(&self) -> String;
fn sql(&self) -> String; // CREATE TABLE statement for column parsing
fn open(&self, conn: Arc<Connection>) -> Result<Arc<RwLock<dyn InternalVirtualTableCursor>>>;
fn best_index(&self, constraints: &[turso_ext::ConstraintInfo],
order_by: &[turso_ext::OrderByInfo]) -> Result<turs_ext::IndexInfo, ResultCode>;
Registration Flow
- Implement
InternalVirtualTablefor your struct - Call
Database::register_internal_vtab(defined in [core/lib.rs](https://github.com/tursodatabase/turso/blob/main/core/lib.rs#L2704-L2708)) - This forwards to
Schema::register_internal_vtab([core/schema.rs](https://github.com/tursodatabase/turso/blob/main/core/schema.rs#L956-L967)), which wraps your object viaVirtualTable::wrap_internal_tableand inserts it into the catalog
Complete Rust Example
use turso::{Database, Connection, Value, Result};
/// Simple read-only internal table returning two rows.
#[derive(Debug)]
struct SimpleTable;
impl turso::vtab::InternalVirtualTable for SimpleTable {
fn name(&self) -> String {
"simple_demo".into()
}
fn sql(&self) -> String {
"CREATE TABLE simple_demo(key TEXT, value INTEGER)".into()
}
fn open(&self, _conn: std::sync::Arc<Connection>)
-> turso::Result<std::sync::Arc<std::sync::RwLock<dyn turso::vtab::InternalVirtualTableCursor>>>
{
// Cursor implementation omitted—see the test suite's StaticCursor for a minimal example
unimplemented!()
}
fn best_index(&self, _c: &[turso_ext::ConstraintInfo],
_o: &[turso_ext::OrderByInfo])
-> std::result::Result<turso_ext::IndexInfo, ResultCode>
{
// No special indexing; any query plan works
Ok(turso_ext::IndexInfo::default())
}
}
fn main() -> Result<()> {
let db = Database::open_file_with_flags(
std::sync::Arc::new(turso::MemoryIO::new()),
turso::util::MEMORY_PATH,
turso::OpenFlags::Create,
turso::DatabaseOpts::new(),
None,
)?;
// Register the table; Turso returns the name actually inserted
let name = db.register_internal_vtab(SimpleTable)?;
assert_eq!(name, "simple_demo");
let conn = db.connect()?;
let mut stmt = conn.prepare("SELECT key, value FROM simple_demo")?;
let rows = stmt.run_collect_rows()?;
Ok(())
}
This pattern mirrors the repository's own test suite (testing/system/vtab.test and the StaticTable/StaticCursor structs in core/vtab.rs).
Creating External Virtual Table Extensions
External virtual tables reside in separate dynamic libraries that implement the VTabModule trait (see [extensions/core/src/vtabs.rs](https://github.com/tursodatabase/turso/blob/main/extensions/core/src/vtabs.rs#L35-L45)). These extensions communicate with Turso through a C-compatible ABI defined in VTabModuleImpl.
Extension Lifecycle
When Turso loads an extension via Database::load_extension ([core/lib.rs](https://github.com/tursodatabase/turso/blob/main/core/lib.rs)), it registers the module name in the global SymbolTable. Upon executing CREATE VIRTUAL TABLE ... USING module_name, Turso calls ExtVirtualTable::create ([core/vtab.rs](https://github.com/tursodatabase/turso/blob/main/core/vtab.rs#L45-L70)), which:
- Invokes the module's
createentry point to obtain schema definitions and an instance pointer - Stores the pointer in an atomic field (
table_ptr) for subsequent operations - Returns a
VirtualTableof typeExternal
The cursor implementation ExtVirtualTableCursor ([core/vtab.rs](https://github.com/tursodatabase/turso/blob/main/core/vtab.rs#L66-L100)) forwards filter, column, next, and eof calls to the function pointers supplied by your extension.
External Module Skeleton (C-ABI)
#include "turso_ext.h"
typedef struct MyTable {
// custom state
} MyTable;
typedef struct MyCursor {
MyTable *tbl;
int pos;
} MyCursor;
VTabCreateResult my_create(const Value *args, int argc) {
MyTable *tbl = malloc(sizeof(MyTable));
const char *schema = "CREATE TABLE x(col TEXT, val INTEGER)";
return (VTabCreateResult){
.code = RESULT_OK,
.schema = schema,
.table = tbl
};
}
void *my_open(void *table, const Conn *conn) {
MyCursor *cur = malloc(sizeof(MyCursor));
cur->tbl = table;
cur->pos = -1;
return cur;
}
ResultCode my_filter(void *cursor, int argc, const Value *argv,
const char *idx_str, int idx_num) {
// Apply constraints, prepare iteration
return RESULT_OK;
}
Value my_column(void *cursor, unsigned int col) {
return Value_from_text("hello");
}
ResultCode my_next(void *cursor) {
// Advance cursor; return RESULT_EOF at end
return RESULT_OK;
}
ResultCode turso_register_module(void *ctx) {
VTabModuleImpl impl = {
.name = "my_mod",
.readonly = false,
.create = my_create,
.open = my_open,
.filter = my_filter,
.column = my_column,
.next = my_next,
// ... other callbacks
};
return RegisterModule(ctx, "my_mod", impl, VTAB_KIND_VIRTUAL_TABLE);
}
Loading External Extensions
After compiling the library (e.g., libmy_mod.so), load it at runtime:
let conn = db.connect()?;
conn.load_extension("libmy_mod.so")?; // registers the module
conn.execute("CREATE VIRTUAL TABLE demo USING my_mod()")?;
let mut stmt = conn.prepare("SELECT * FROM demo")?;
let rows = stmt.run_collect_rows()?;
Core Architecture and Wiring
Understanding how Turso wires virtual tables together helps debug extension behavior and optimize query plans:
-
VirtualTable([core/vtab.rs](https://github.com/tursodatabase/turso/blob/main/core/vtab.rs#L19-L25)): The unified representation holding table metadata and theVirtualTableTypediscriminant (pragma, external, or internal) -
VirtualTable::open: Dispatches to cursor constructorsnew_pragma,new_external, ornew_internalbased on the table type -
VirtualTableCursor: A thin wrapper trait that forwardsnext,column,filter, androwidto concrete implementations (PragmaVirtualTableCursor,ExtVirtualTableCursor, or internal cursors) -
ExtVirtualTable: Holds the implementation pointer (VTabModuleImpl) and opaque table instance (table_ptr). Its methods invoke the extension's C functions (lines 45-71 incore/vtab.rs) -
VTabModuletrait and macros: The#[derive(VTabModule)]macro in [macros/src/ext/vtab_derive.rs](https://github.com/tursodatabase/turso/blob/main/macros/src/ext/vtab_derive.rs) generates the requiredVTabModuleImplstructure for Rust-based extensions
Summary
-
Three virtual table kinds: Pragma (system metadata), External (dynamic libraries), and Internal (Rust structs)
-
Internal registration: Implement
InternalVirtualTable, then callDatabase::register_internal_vtabto insert into the catalog viaSchema::register_internal_vtab -
External modules: Implement
VTabModuleImplin a shared library, load withConnection::load_extension, and create tables usingCREATE VIRTUAL TABLE ... USING module_name -
Key implementation files:
core/vtab.rs(core types and dispatch),core/schema.rs(registration),core/lib.rs(public API), andextensions/core/src/vtabs.rs(module interface) -
Cursor lifecycle: Turso calls
opento create a cursor,filterto apply constraints, then iterates withnextandcolumnuntileofreturns true
Frequently Asked Questions
What is the difference between internal and external virtual tables in Turso?
Internal virtual tables are Rust structs implementing the InternalVirtualTable trait that compile directly into your application binary, ideal for exposing lightweight state like metrics or configuration. External virtual tables are dynamic libraries implementing the C-compatible VTabModuleImpl interface, suitable for complex data sources or extensions written in other languages that must be loaded at runtime.
How do I register a custom virtual table with Turso?
For internal tables, call Database::register_internal_vtab with your struct implementing InternalVirtualTable, which stores the table in the schema catalog via Schema::register_internal_vtab. For external tables, compile your extension as a shared library, load it using Connection::load_extension, then execute CREATE VIRTUAL TABLE with your module name.
Can external virtual tables interact with regular Turso tables?
Yes. When Turso calls your extension's open callback ([core/vtab.rs](https://github.com/tursodatabase/turso/blob/main/core/vtab.rs)), it passes a Conn pointer that allows the extension to execute queries against the database. This enables joining external data with standard tables or accessing schema information from within the virtual table implementation.
Where are virtual table cursor implementations defined in the source code?
Cursor implementations reside in [core/vtab.rs](https://github.com/tursodatabase/turso/blob/main/core/vtab.rs). The file contains ExtVirtualTableCursor (lines 66-100) for external modules, PragmaVirtualTableCursor for system tables, and the InternalVirtualTableCursor trait for Rust-based internal tables. The VirtualTableCursor enum dispatches calls to these concrete implementations based on the virtual table type.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →