hematite-db 0.1.0

A small embeddable SQL database.
Documentation
  • Coverage
  • 4.55%
    71 out of 1560 items documented1 out of 666 items with examples
  • Size
  • Source code size: 1.95 MB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 73.4 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 1m 2s Average build duration of successful builds.
  • all releases: 1m 2s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • nathy-cpu/hematite
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • nathy-cpu

Hematite

Hematite is a small embeddable SQL database written in Rust.

The goal is not to be a giant general-purpose server. The goal is to stay small enough to understand, repurpose, and extend, while still supporting a robust relational feature set.

What It Is Good At

  • Direct Embedding: Seamlessly integrate into Rust applications without external dependencies.
  • Lightweight Storage: Perfect for local-first applications and single-user tools.
  • Internals Exploration: A readable codebase designed for experimentation with SQL and B-tree internals.
  • Explicit Semantics: A strict, predictable SQL dialect that avoids the "magic" of larger RDBMSs.

Quick Start

Add the crate to your Cargo.toml:

[dependencies]
hematite = { package = "hematite-db", version = "0.1" }

The published package name is hematite-db, while the library is imported in Rust code as hematite.

Basic Usage

use hematite::Hematite;

fn main() -> hematite::Result<()> {
    let mut db = Hematite::new_in_memory()?;

    db.execute("CREATE TABLE users (id INT PRIMARY KEY, name TEXT, balance DECIMAL(10, 2));")?;
    db.execute("INSERT INTO users (id, name, balance) VALUES (1, 'Ada', 1000.50);")?;

    // Flexible query API
    let rows = db.query("SELECT id, name FROM users WHERE balance > 500;")?;
    for row in rows.iter() {
        println!("User {}: {}", row.get_as::<i32>(0)?, row.get_as::<String>(1)?);
    }

    Ok(())
}

Transactions and Savepoints

fn transfer_funds(db: &mut Hematite, from: i32, to: i32, amount: f64) -> hematite::Result<()> {
    let mut tx = db.transaction()?;

    tx.execute(&format!("UPDATE users SET balance = balance - {amount} WHERE id = {from};"))?;
    
    // Create a savepoint for complex multi-step logic
    tx.execute("SAVEPOINT intermediate;")?;
    
    match tx.execute(&format!("UPDATE users SET balance = balance + {amount} WHERE id = {to};")) {
        Ok(_) => {
            tx.execute("RELEASE SAVEPOINT intermediate;")?;
            tx.commit()?;
        }
        Err(_) => {
            tx.execute("ROLLBACK TO SAVEPOINT intermediate;")?;
            // Handle error or commit the partial transaction
        }
    }
    
    Ok(())
}

Prepared Statements with Parameters

fn find_user(db: &mut Hematite, user_id: i32) -> hematite::Result<()> {
    let mut stmt = db.prepare("SELECT name FROM users WHERE id = ?;")?;
    
    // Bind parameters by index (starting at 1)
    stmt.bind(1, hematite::Value::Integer(user_id))?;
    
    let result = db.execute_prepared(&mut stmt)?;
    // ... process result
    
    Ok(())
}

Persistent Storage

By default, Hematite::new_in_memory() creates a transient database. For persistence, simply specify a file path:

let mut db = Hematite::new("prod_data.db")?;

Struct Mapping

Map database rows directly to Rust structs by implementing the FromRow trait.

use hematite::{Hematite, FromRow, Row, Result};

struct User {
    id: i32,
    name: String,
}

impl FromRow for User {
    fn from_row(row: &Row) -> Result<Self> {
        Ok(Self {
            id: row.get_int(0)?,
            name: row.get_string(1)?,
        })
    }
}

fn list_users(db: &mut Hematite) -> Result<Vec<User>> {
    db.query_as("SELECT id, name FROM users;")
}

ASCII Table Rendering

For CLI tools or debugging, you can render a result set as a formatted ASCII table:

let results = db.query("SELECT id, name, balance FROM users;")?;
println!("{}", results.render_ascii_table());
+------+------+---------+
| id   | name | balance |
+------+------+---------+
| 1    | Ada  | 1000.50 |
| 2    | Bob  | 250.75  |
+------+------+---------+
2 row(s)

CLI Usage

Hematite ships with a lightweight CLI tool (hematite_cli) for schema exploration and ad-hoc queries.

Interactive Shell

Start the interactive REPL with a database file:

cargo run --bin hematite_cli -- demo.db

One-off Commands

Execute a single SQL command without entering the shell:

cargo run --bin hematite_cli -- demo.db "SELECT * FROM users;"

Advanced SQL Features

Hematite supports powerful SQL constructs that are often missing in "small" databases:

Recursive CTEs

Find all subordinates in an organizational hierarchy.

WITH RECURSIVE subordinates AS (
    SELECT id, name, manager_id FROM employees WHERE name = 'Alice'
    UNION ALL
    SELECT e.id, e.name, e.manager_id 
    FROM employees e
    INNER JOIN subordinates s ON s.id = e.manager_id
)
SELECT * FROM subordinates;

Window Functions

Calculate a running total of sales.

SELECT 
    sale_date, 
    amount, 
    SUM(amount) OVER (ORDER BY sale_date) as running_total
FROM sales;

Precision Math and Intervals

Handle financial calculations and temporal offsets accurately.

SELECT 
    price * 1.05 as price_with_tax,
    current_date + INTERVAL '1-02' YEAR TO MONTH as expiry_date
FROM products;

Schema Introspection

Easily explore your database structure using built-in metadata commands:

SHOW TABLES;
DESCRIBE users;
SHOW CREATE TABLE users;
EXPLAIN SELECT * FROM users WHERE id = 1;

Current Limitations

Hematite already supports a broad SQL surface, but there are still important things users often expect from an embedded database that are not supported yet.

Application Ergonomics

  • Manual struct mapping: Typed row mapping exists, but users currently need to implement FromRow manually.
  • No derive macros yet: There is no #[derive(FromRow)]-style support yet.
  • No schema-from-struct workflow: Hematite does not yet generate tables from Rust structs.
  • No built-in JSON row/result API: There is no first-class JSON export/import convenience layer yet.

Embedded App Workflow

  • No migration framework: DDL is supported, but there is no built-in migration/versioning workflow.
  • No backup / restore helpers: There is not yet a high-level API for backups, dumps, or restore flows.
  • No import/export tools: CSV import/export and similar convenience tooling are not built in.
  • No async API: The crate is synchronous today.

Database Features Users May Expect

  • Views are read-only
  • Trigger bodies are intentionally limited to single-statement row-level AFTER triggers
  • No JSON type or JSON operators
  • No stored procedures or user-defined functions
  • No generated columns
  • No full information-schema subsystem

Deployment Model

  • Embedded only: Hematite is not a client/server database.
  • No users, roles, or network protocol
  • No replication or clustering

These are known limits, not accidents. The project intentionally favors a small, understandable, embedded-first design over chasing every feature found in larger database systems.

Documentation

Status

Hematite is an evolving library. While it is stable enough for experimentation, the dialect and public API are still being refined as we head toward a 1.0 release.

License

MIT. See LICENSE.