COOLJAPAN
← All posts

OxiSQL 0.4.1 — A New Cache Crate, Triggers, Multi-Database ATTACH, and Six Process-Abort Fixes

OxiSQL 0.4.1 ships oxisql-cache — a first-publish crate that inverts a former oxisql⇄oxistore dependency cycle — plus CREATE TRIGGER/row-trigger execution, TEMP objects and ATTACH/DETACH DATABASE multi-database support, PRAGMA index_list/index_info, fuzz targets, and quickstart examples for every driver. Six process-abort and undefined-behavior fixes land in the C-free oxisqlite engine. Part of the NoFFI sovereign Rust stack.

release oxisql pure-rust cooljapan noffi sql database sqlite postgres mysql cache

A SQL engine that cannot fire a trigger, attach a second database, or cache a query result is not incomplete by design — it is incomplete by TODO list. OxiSQL 0.4.1 closes three of the biggest ones at once, and ships a brand-new crate along the way.

Today — August 7, 2026 — we released OxiSQL 0.4.1, the sovereign Pure-Rust SQL layer for the COOLJAPAN ecosystem. This is the largest feature release since 0.3.3: CREATE TRIGGER and full row-trigger execution, TEMP objects and ATTACH/DETACH DATABASE multi-database support, PRAGMA index_list/index_info index introspection, a new oxisql-cache crate, fuzz targets, runnable quickstart examples for every facade/driver crate, and six process-abort/undefined-behavior fixes in the C-free oxisqlite storage engine.

No C. No Fortran. No libpq, no libmysqlclient, no libsqlite3 — the workspace still compiles cleanly with the C compiler disabled (CC=/usr/bin/false cargo build --workspace → exit 0), across 18 crates now instead of 17, backed by 2,261 tests passing with default features (2,755 with --all-features), 0 failing, 0 clippy warnings.

Why 0.4.1 is a game changer

Before this release, three gaps forced real applications to reach outside OxiSQL entirely:

OxiSQL 0.4.1 ends all of that:

Technical Deep Dive: how triggers and ATTACH actually work

  1. Triggers are inlined, not framed. This VDBE has no OP_Program/frame stack, so trigger bodies are inlined into the firing program via the existing incr_nesting() mechanism. OLD.*/NEW.* are rewritten into a new code-generator-only ast::Expr::Register(usize) node before translation, so every existing planner/optimizer/emitter path handles a trigger body with zero trigger-specific plumbing. A trigger cannot re-enter itself (recursive_triggers = off by default), while non-recursive nesting — A fires B fires C — works.
  2. Every database slot owns its own pager, catalog, and transaction state. Index 0 is main, index 1 is temp (created lazily, backed by a private in-memory pager), and indices 2+ are ATTACHed databases — each a nested Database that reuses the tested top-level open path for WAL setup, header bootstrap, and schema parsing.
  3. Name resolution follows upstream exactly. A schema qualifier (main.t, temp.t, alias.t) addresses that database directly; an unqualified name searches tempmain → attached, except sqlite_schema/sqlite_master, which stay pinned to main the way upstream keeps them.
  4. oxisql-cache wraps oxistore-cache’s Pure-Rust LRU primitive. SqlQueryCache caches RowSet results keyed by normalized SQL text; SqlPlanCache<P> caches an opaque, caller-supplied prepared-statement representation; CachedQueryRunner turns any FnMut(&str) -> Result<RowSet, E> executor into a caching one with hit/miss counters — all #![forbid(unsafe_code)].

Getting Started

cargo add oxisql --features sqlite,pool-sqlite-compat

Or pin the exact release in Cargo.toml:

[dependencies]
oxisql = { version = "0.4.1", features = ["embedded", "postgres", "pool-embedded", "migrate"] }

The C-free SQLite path, unchanged in shape, still runs on the same oxisqlite engine:

use oxisql_core::Connection;
use oxisql_sqlite_compat::SqliteConnection;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let conn = SqliteConnection::open_memory().await?;

    conn.execute(
        "CREATE TABLE pilots (id INTEGER PRIMARY KEY, name TEXT NOT NULL, callsign TEXT)",
        &[],
    )
    .await?;
    conn.execute(
        "INSERT INTO pilots (name, callsign) VALUES ($1, $2)",
        &[&"Maverick", &"Pete Mitchell"],
    )
    .await?;

    let rows = conn
        .query("SELECT id, name, callsign FROM pilots ORDER BY id", &[])
        .await?;
    println!("pilots: {}", rows.len());
    Ok(())
}

New in 0.4.1 — a trigger that keeps an audit log in sync with every insert, entirely inside the database:

CREATE TABLE t (a INTEGER);
CREATE TABLE log (val INTEGER);
CREATE TRIGGER t_ai AFTER INSERT ON t
  BEGIN
    INSERT INTO log VALUES (NEW.a);
  END;

INSERT INTO t VALUES (42);  -- log now contains 42, no application code involved

And oxisql-cache’s read-through adapter, wrapping any query executor with an LRU cache:

use oxisql_core::{Row, RowSet, Value};
use oxisql_cache::CachedQueryRunner;

let mut runner = CachedQueryRunner::new(32, |sql: &str| -> Result<RowSet, String> {
    Ok(RowSet::from_rows(vec![Row::new(vec!["n".into()], vec![Value::I64(1)])]))
});

let r1 = runner.run("SELECT 1").unwrap();
let r2 = runner.run("SELECT 1").unwrap(); // served from cache
assert_eq!(runner.hits(), 1);
assert_eq!(runner.misses(), 1);

What’s New in 0.4.1

Tips

This is the foundation

OxiSQL’s facade/driver layer is now 11 crates — up from 10, with the addition of oxisql-cache — sitting on the 7-crate, C-free oxisqlite-* engine (18 workspace crates total, plus two non-published patch-shim crates). OxiSQL sits on OxiTLS (transport/TLS), oxicode (row serde), OxiCrypto (encryption-at-rest), and OxiStore (lower storage layer, now consumed the right direction via oxisql-cacheoxistore-cache). It is depended on by oxirs, oxify, oxigeo, oximedia, celers, legalis, oxicar, oxi3d, oxiaero, scirs, oxiproj, tensorlogic, and trustformers — a growing list of COOLJAPAN services that need SQL without a C dependency in sight.

Repository: https://github.com/cool-japan/oxisql

Star the repo if you want triggers, multi-database ATTACH, and query caching that all ship Pure Rust, with zero libsqlite3 anywhere in the graph.

The era of reimplementing trigger logic in application code because the database wouldn’t do it for you is over. Pure Rust SQL — sovereign, safe, and now considerably more capable — is here.

KitaSan at COOLJAPAN OÜ August 7, 2026

↑ Back to all posts