COOLJAPAN
← All posts

OxiSQL 0.3.2 Released — The Last C-FFI Crate Is Gone, and COUNT(*) Finally Counts Right

OxiSQL 0.3.2 closes the final C-FFI gap in its --all-features dependency closure with a new Pure-Rust zstd-shim replacing zstd-sys (pulled in by DataFusion's Arrow IPC), and fixes a GROUP BY … HAVING COUNT(*) accumulator-reset bug in the oxisqlite-core query engine. The sovereign SQL layer for the COOLJAPAN ecosystem.

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

The last C library left in OxiSQL’s dependency closure wasn’t a database driver at all — it was a compression codec, buried several dependency hops downstream inside DataFusion’s Arrow IPC layer.

Today we released OxiSQL 0.3.2 — a release that closes the final C-FFI gap in the --all-features dependency closure with a new Pure-Rust zstd-shim, and fixes a GROUP BY … HAVING COUNT(*) accumulator-reset bug in the oxisqlite-core query engine.

No C. No Fortran. No libpq, no libmysqlclient, no libsqlite3, and — as of 0.3.2 — no zstd-sys anywhere in the workspace’s dependency graph, --all-features included. OxiSQL’s SQLite path already ran on oxisqlite, an in-tree, from-scratch C-free fork of limbo; 0.3.2 plugs the one remaining transitive leak, a C-FFI compression codec that arrow-ipc pulled in for DataFusion. It compiles to a single static binary and runs everywhere Rust runs.

Why OxiSQL 0.3.2 is a game changer

Building OxiSQL with the datafusion feature — or just --all-features — meant living with a quiet compromise:

OxiSQL 0.3.2 ends all of that:

This is not the first C dependency OxiSQL has cut, and the mechanism is the same each time: a small, local, unpublished patch crate wired in via [patch.crates-io]. 0.2.0 replaced the entire SQLite backend this way — the oxisqlite fork of limbo, dropping libsqlite3, the mimalloc C allocator, and the lemon C parser generator in one move. 0.3.0 dropped objc2-system-configuration from the macOS build via whoami-patched. 0.3.2 is the third pass of the same knife, aimed at zstd-sys.

Technical Deep Dive: how a compression codec and a query planner both got fixed in one release

  1. crates/zstd-shim/ masquerades as the real thing, on purpose. Its Cargo.toml names the package zstd, versions it 0.13.3 to satisfy arrow-ipc’s semver requirement, and depends on oxiarc-zstd = "0.3.5". Two files — src/lib.rs and src/bulk.rs — implement exactly the bulk::{Compressor, Decompressor} API and DEFAULT_COMPRESSION_LEVEL constant that arrow-ipc calls, nothing more. The workspace root Cargo.toml redirects every consumer to it with one line under [patch.crates-io], alongside the existing rustls-rustcrypto-patched and whoami-patched entries.
  2. The query planner (oxisqlite-core/translate/group_by.rs) computes the aggregate-block span for every GROUP BY. Before 0.3.2, a COUNT(*) reached through HAVING, ORDER BY, or a nested expression carried zero arguments into that span calculation instead of the synthetic literal-1 argument the plain result-column path already attached — undercounting how many sorter columns the aggregate block actually needed.
  3. The VDBE executor (oxisqlite-core/vdbe/execute/aggregate.rs) runs the per-row accumulator step, op_agg_step. Its group_by_emit_row_phase clear range now spans the full aggregate block computed in step 2, rather than trusting the previously short sorter-column count — so a stale accumulator from the prior group can’t leak into the next one.
  4. Regression coverage lives in oxisqlite-core/tests/group_by_having.rs, built around exactly the multi-group GROUP BY … HAVING COUNT(*) … shapes that used to trigger out-of-bounds sorter reads and stale-accumulator panics.

Getting Started

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

The C-free SQLite path — oxisqlite, the same engine this release’s GROUP BY fix lives in — supports real transactional ROLLBACK:

use oxisql::SqliteConnection;
use oxisql::prelude::*;

#[tokio::main]
async fn main() -> Result<(), oxisql::OxiSqlError> {
    let conn = SqliteConnection::open_memory().await?;
    conn.execute("CREATE TABLE t (id INTEGER, name TEXT)", &[]).await?;

    // BEGIN ... ROLLBACK discards the INSERT.
    conn.execute("BEGIN", &[]).await?;
    conn.execute("INSERT INTO t VALUES (1, 'Alice')", &[]).await?;
    conn.execute("ROLLBACK", &[]).await?;

    let rows = conn.query("SELECT COUNT(*) FROM t", &[]).await?;
    println!("rows after ROLLBACK: {:?}", rows); // → one row holding COUNT(*) = 0
    Ok(())
}

Reach the same engine through the facade with oxisql::connect("sqlite::memory:") or oxisql::connect("sqlite://path/to/file.db"). For the DataFusion OLAP path that made zstd-shim necessary, add the datafusion feature and call oxisql::connect_datafusion("datafusion://") to obtain an OxiSqlContext.

What’s New in 0.3.2

Tips

This is the foundation

OxiSQL’s facade/driver layer is 10 crates (oxisql, oxisql-core, oxisql-parse, oxisql-embedded, oxisql-postgres, oxisql-mysql, oxisql-datafusion, oxisql-pool, oxisql-migrate, oxisql-sqlite-compat) sitting on the 7-crate, C-free oxisqlite-* engine, alongside a perf benchmark crate and three vendored [patch.crates-io] crates: rustls-rustcrypto-patched, whoami-patched, and — new this release — zstd-shim.

OxiSQL itself sits on OxiTLS (transport/TLS), oxicode (row serde), OxiCrypto (encryption-at-rest), OxiStore (lower storage layer), and, as of 0.3.2, OxiARC’s oxiarc-zstd 0.3.5 (backing zstd-shim, replacing zstd-sys on the DataFusion IPC path). It is depended on by oxirouter, oxirs, oxify, oxigdal-db-connectors, oximedia, oxigaf, and oxirag — the database tier each of those reaches for when it needs SQL.

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

Star the repo if you want a database layer where turning on an OLAP feature doesn’t quietly hand you a C compression library, and a GROUP BY that counts the same way on group 1 and group 100.

The era of “Pure Rust, except for whatever your transitive dependencies pulled in” is over. Pure Rust SQL — sovereign, safe, and FFI-free — is here.

KitaSan at COOLJAPAN OÜ July 11, 2026

↑ Back to all posts