#in-memory-database #sql-query #sql #database-query #serde #in-memory

fosk

In-memory SQL-like query engine and lightweight data store for testing and prototyping

14 releases

Uses new Rust 2024

0.1.13 Sep 22, 2025
0.1.12 Sep 17, 2025

#262 in Database interfaces

Download history 21/week @ 2025-08-28 422/week @ 2025-09-04 850/week @ 2025-09-11 320/week @ 2025-09-18 51/week @ 2025-09-25 25/week @ 2025-10-02 6/week @ 2025-10-09 14/week @ 2025-10-16 7/week @ 2025-10-23

882 downloads per month
Used in rs-mock-server

MIT license

610KB
13K SLoC

FOSK logo

FOSK

fosk is a lightweight embedded SQL engine for Rust applications. It allows you to define in-memory collections, seed them with JSON objects, and query using a SQL-like syntax.


โœจ Features

  • In-memory database with collections (tables)
  • Configurable ID strategies: integer, UUID, or none
  • Simple JSON storage (serde_json::Value)
  • SQL parser with support for:
  • SELECT, WHERE, GROUP BY, HAVING
  • JOIN (inner, left, right, full)
  • ORDER BY, LIMIT, OFFSET
  • Parameterized queries (? placeholders, including arrays)
  • Test-friendly: create databases on the fly and seed them

Installation

In your Cargo.toml:

[dependencies]
fosk = "0.1.2"
serde_json = "1"

Quick example

use fosk::{Db, DbConfig, IdType};
use serde_json::json;

fn main() {
    let db = Db::new_db_with_config(DbConfig {
        id_type: IdType::Int,       // auto-increment IDs
        id_key: "id".into(),
    });

    let people = db.create_collection("People");

    // Add JSON
    let added = people.add(json!({"name": "Alice", "age": 30})).unwrap();

    // Retrieve auto-generated ID (if configured)
    let id = added.get("id").unwrap().to_string();
    println!("Added person with ID: {}", id);

    // Add multiple documents
    let added_many = people.add_batch(json!([
        {"name": "Bob", "age": 25},
        {"name": "Carol", "age": 28}
    ]));
    println!("Added many: {:?}", added_many);

    // Query with parameters
    let rows = db.query_with_args(
        "SELECT id, name, age FROM People WHERE id = ?",
        json!(id),
    ).unwrap();

    println!("Query result: {rows:?}");
}

๐Ÿ“š Main Types & API

Db

Represents a database.

  • new_db_with_config(config: DbConfig) -> Db
  • create_collection(name: &str) -> CollectionHandle
  • query(sql: &str) -> Result<Vec, AnalyzerError>
  • query_with_args(sql: &str, args: Value) -> Result<Vec, AnalyzerError>
  • drop_collection(col_name: &str) -> bool
  • clear()
  • get_config() -> DbConfig

Config

Defines collection behavior.

  • DbConfig::int("id") โ†’ auto-increment integer IDs
  • DbConfig::uuid("id") โ†’ UUID v4 IDs
  • DbConfig::none("id") โ†’ no automatic ID field

CollectionHandle

  • add(item: Value) -> Option
  • get_all() -> Vec
  • get(id: &str) -> Option
  • update(id: &str, item: Value) -> Option
  • delete(id: &str) -> Option

extras

  • get_paginated(offset: usize, limit: usize) -> Vec
  • exists(id: &str) -> bool
  • count() -> usize
  • add_batch(items: Value) -> Vec
  • update_partial(id: &str, partial_item: Value) -> Option
  • clear() -> usize
  • get_config() -> DbConfig

Load from existing data

fn Db::load_from_file(path: &std::path::Path) -> Result<String, String>;

let db = Db::new();
// Load all collections from a file
db.load_from_file("./collection.json");

fn Db::load_from_json(json_value: &serde_json::Value) -> Result<usize, String>

let db = Db::new();
// Load all collections from JSON
db.load_from_json(json!({
    "people": [...],
    "companies": [...],
    "products": [...],
}));

fn DbCollection::load_from_file(path: &std::path::Path) -> Result<String, String>;

let db = Db::new();
let people = db.create_collection("People");
// Load collection from a file
people.load_from_file("./people.json");

fn DbCollection::load_from_json(json_value: &serde_json::Value) -> Result<String, String>

let db = Db::new();
let people = db.create_collection("People");
// Load collection from JSON
people.load_from_json(json!([
    { "id": 1,  "full_name": "Alice Johnson",    "age": 29, "city": "Porto",    "vip": true  },
    { "id": 2,  "full_name": "Bruno Martins",    "age": 34, "city": "Lisboa",   "vip": false },
    { "id": 3,  "full_name": "Carla Sousa",      "age": 41, "city": "Braga",    "vip": false },
    { "id": 4,  "full_name": "David Pereira",    "age": 25, "city": "Coimbra",  "vip": true  }
]));

Save data

fn Db::fn write_to_json(&self) -> Value

let db = Db::new();
...
...
...
let json_value = db.write_to_json();

fn DbCollection::write_to_file(&self, file_path: &OsString) -> Result<(), String>

let db = Db::new();
...
...
...
db.write_to_file("./collections.json");

fn DbCollection::write_to_file(path: &std::path::Path) -> Result<(), String>

let db = Db::new();
let people = db.create_collection("People");
...
...
people.write_to_file("./people.json");

๐Ÿงช Testing & Seeding

Example test seed (see fixtures::seed_db):

pub fn seed_db() -> Db {
    let db = Db::new_db_with_config(DbConfig {
        id_type: IdType::None,
        id_key: "id".into(),
    });

    create_people(&db);
    create_products(&db);
    create_orders(&db);
    create_order_items(&db);

    db
}

โš ๏ธ Notes

  • Projections normally output unqualified field names (id, name), unless duplicates exist. In case of conflicts, names are disambiguated with their collection prefix (id, o.id).

๐Ÿ“„ License

Licensed under the MIT License. See LICENSE for details.

Dependencies

~12โ€“27MB
~266K SLoC