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)
- Non-correlated FROM/JOIN subqueries with required aliases
- ORDER BY, LIMIT, OFFSET
- Parameterized queries (? placeholders, including arrays)
- Test-friendly: create databases on the fly and seed them
Installation
In your Cargo.toml:
[]
= "0.1.15"
= "1"
Quick example
use ;
use json;
For a larger executable walkthrough of the public API, run:
The app is an independent Cargo project under examples/full_demo. It includes
mock collection and schema files under examples/full_demo/mocks, including
UUID IDs, auto-increment IDs, caller-provided None:* IDs, custom ID field
names, nested objects, arrays, nullable fields, and relationship-shaped data.
It also runs SQL examples over the loaded files: boolean filters, LIKE,
IS NULL, parameterized IN (?), NOT IN, joins, COUNT(DISTINCT ...),
SUM, AVG, GROUP BY, HAVING, ORDER BY, OFFSET, and LIMIT.
Example app map:
- Database handles and ID strategies:
examples/full_demo/src/database_and_ids.rs - Collection CRUD:
examples/full_demo/src/collection_crud.rs - JSON file loading/saving and fixture catalog:
examples/full_demo/src/load_save.rs - Queries over complex loaded fixtures:
examples/full_demo/src/complex_queries.rs - Sales-style join/aggregate queries:
examples/full_demo/src/queries.rs - Reference creation, inference, and expansion:
examples/full_demo/src/references.rs - Schema loading from JSON values and files:
examples/full_demo/src/schema_loading.rs - Public schema metadata helpers:
examples/full_demo/src/metadata.rs - Collection fixtures:
examples/full_demo/mocks/collections - Schema fixtures:
examples/full_demo/mocks/schemas
๐ Public API Guide
Create a database
Db is the user-facing database handle. It owns named collections, stores the default collection configuration, runs SQL queries, and manages schema/reference metadata.
use ;
let default_db = new;
assert_eq!;
let int_db = new_with_config;
assert_eq!;
let shared = new_arc;
shared.create;
assert!;
Runnable example: examples/full_demo/src/database_and_ids.rs
Configure IDs
DbConfig controls how a collection handles IDs. The database config is copied into collections created with Db::create; use Db::create_with_config when one collection needs a different strategy.
use ;
use json;
let db = new_with_config;
let people = db.create;
let inserted = people.add.unwrap;
assert_eq!;
let sessions = db.create_with_config;
let session = sessions.add.unwrap;
assert!;
let logs = db.create_with_config;
assert!;
assert!;
assert_eq!;
Available constructors:
DbConfig::new()uses UUID IDs in theidfield.DbConfig::int("id")uses auto-increment integer IDs.DbConfig::uuid("id")uses generated UUID strings.DbConfig::none("id")requires callers to provide the ID field.
Runnable examples:
- ID strategies in code:
examples/full_demo/src/database_and_ids.rs - Mixed ID fixture loading:
examples/full_demo/src/load_save.rs - Fixtures with UUID,
_id, and caller-provided IDs:examples/full_demo/mocks/collections
Manage collections
Collection names are stored case-insensitively. Creating an existing collection name replaces it with a new empty collection.
use ;
use json;
let db = new_with_config;
let people = db.create;
people.add;
assert!;
assert_eq!;
assert!;
assert!;
db.create;
db.clear;
assert!;
Runnable example: examples/full_demo/src/database_and_ids.rs
Work with documents
DbCollection exposes read, write, pagination, replacement, partial update, and deletion helpers. IDs are looked up as strings even when stored as numbers.
use ;
use json;
let people = new_coll;
people.add;
people.add_batch;
assert_eq!;
assert!;
assert_eq!;
assert_eq!;
let updated = people
.update_partial
.unwrap;
assert_eq!;
assert_eq!;
let replaced = people
.update
.unwrap;
assert_eq!;
assert!;
assert_eq!;
Runnable example: examples/full_demo/src/collection_crud.rs
Load existing data
use ;
use json;
use OsString;
let db = new;
let loaded = db.load_from_json.unwrap;
assert_eq!;
let people = new_coll;
let inserted = people
.load_from_json
.unwrap;
assert_eq!;
// File APIs accept OsString paths and return human-readable status strings.
// db.load_from_file(&OsString::from("collections.json"))?;
// people.load_from_file(&OsString::from("people.json"))?;
The keep flag controls incoming IDs:
truepreserves IDs from loaded documents where possible.falseallows IDs to be regenerated according to the collection config.
Runnable examples:
- DB and collection file loading:
examples/full_demo/src/load_save.rs - Coherent whole-DB fixture:
examples/full_demo/mocks/collections/database.json - Sales DB fixture used by query/reference examples:
examples/full_demo/mocks/collections/sales_database.json - Standalone collection fixtures with different ID conventions:
examples/full_demo/mocks/collections
Save data
use ;
use json;
use OsString;
let db = new_with_config;
let people = db.create;
people.add;
let dump = db.write_to_json;
assert_eq!;
// db.write_to_file(&OsString::from("collections.json"))?;
// people.write_to_file(&OsString::from("people.json"))?;
Runnable example: examples/full_demo/src/load_save.rs
Query data
Use query for SQL without placeholders and query_with_args for positional ? parameters. Pass one JSON value for one placeholder, or a JSON array for multiple placeholders. Arrays can also be used inside IN (?).
use ;
use json;
let db = new_with_config;
db.create.add_batch;
let older = db
.query
.unwrap;
assert_eq!;
let selected = db
.query_with_args
.unwrap;
assert_eq!;
Supported SQL includes SELECT, WHERE, GROUP BY, HAVING, joins, non-correlated FROM/JOIN subqueries with aliases, ORDER BY, LIMIT, OFFSET, aggregate functions, aliases, and positional parameters.
Runnable examples:
- Queries over file-loaded complex fixtures:
examples/full_demo/src/complex_queries.rs - Sales-style joins and aggregate reports:
examples/full_demo/src/queries.rs - Query fixture catalog:
examples/full_demo/mocks/collections
References and expansion
References are foreign-key-like mappings between collection fields. They can be declared manually or inferred from collection naming conventions, then used to expand rows with related records.
use ;
use json;
let db = new_with_config;
let people = db.create;
let orders = db.create;
people.add;
orders.add;
assert!;
assert!;
let expanded = orders.expand_row;
assert_eq!;
infer_reference("orders", "people") looks for the referenced collection's conventional reference field name. For example, a people collection with ID key id expects people_id; a users collection with ID key user_id expects user_id.
Runnable examples:
- Manual references, inferred references, and row/list expansion:
examples/full_demo/src/references.rs - Relationship-shaped sales fixture:
examples/full_demo/mocks/collections/sales_database.json
Load collection schemas
Collection schemas can be loaded before inserting data. They define field names, field types, nullability, and optionally the collection ID behavior. References are not written in schema files; when schemas are loaded through Db, references are inferred after the load using the same inference rules already used by the database.
Compact collection schema format
A single collection schema is a JSON object where each key is a field name and each value is a compact type string:
Supported regular field types are:
NullBoolIntFloatStringObjectArray
Nullability is declared with !:
"age": "Int"meansageis nullable."age": "Int!"meansageis non-nullable.
ID markers
One field can be marked as the collection ID field:
"id": "Id"uses auto-increment integer IDs and stores the field asInt!."uuid": "Uuid"uses generated UUID IDs and stores the field asString!."external_id": "None:String"uses caller-provided IDs and stores the field asString!."legacy_id": "None:Int"uses caller-provided IDs and stores the field asInt!.
None:Type ID markers are always non-nullable because the field is the collection ID. Nullable forms such as None:Int!, None:String!, and None:Null are rejected.
Load one collection schema
use Db;
use json;
let db = new;
db.load_collection_schema_from_json;
For direct JSON values, the collection name must be provided because there is no filename or parent object key to infer it from.
Runnable example: examples/full_demo/src/schema_loading.rs
Schema files for one collection contain only the compact field map:
The collection name is inferred from the file stem:
use Db;
let db = new;
// Loads into the `users` collection.
// db.load_collection_schema_from_file(&"users.json".into())?;
Runnable examples:
- Single schema file loading:
examples/full_demo/src/schema_loading.rs - Single-collection schema fixtures:
examples/full_demo/mocks/schemas
Load all collection schemas
To load the whole database schema, use a JSON object keyed by collection name:
use Db;
use json;
let db = new;
db.load_schemas_from_json;
The same structure can be loaded from a file:
use Db;
let db = new;
// db.load_schemas_from_file(&"schema.json".into())?;
Runnable examples:
- Whole-DB schema loading:
examples/full_demo/src/schema_loading.rs - Whole-DB schema fixture:
examples/full_demo/mocks/schemas/database_schema.json - Alternate REST-resource schema fixture:
examples/full_demo/mocks/schemas/rest_resources.json
Load schema on an existing collection
Collection handles can load only their own schema:
use Db;
use json;
let db = new;
let users = db.create_with_config;
users.load_schema_from_json;
Collection-level schema loading validates any ID marker against the collection's existing DbConfig; it does not change the collection config, stored rows, or ID generator state.
Runnable examples:
- Schema loading APIs in one place:
examples/full_demo/src/schema_loading.rs - ID marker variants in schema files:
examples/full_demo/mocks/schemas
Inspect schemas
use ;
use json;
let db = new_with_config;
let people = db.create;
people.add;
let schema = people.schema.unwrap;
assert_eq!;
let schema_with_refs = db.schema_with_refs_of.unwrap;
assert_eq!;
Runnable examples:
- Schema inspection after loading:
examples/full_demo/src/schema_loading.rs - Direct metadata helper usage:
examples/full_demo/src/metadata.rs
Useful metadata types:
JsonPrimitiveclassifies fields asNull,Bool,Int,Float,String,Object, orArray.FieldInfostores a field's primitive type and nullability.SchemaDictstores field metadata for one collection.SchemaWithRefscombines a collection schema with inbound and outbound references.ReferenceColumndescribes one relationship between two collection fields.
๐งช Testing & Seeding
Example test seed (see fixtures::seed_db):
Runnable examples:
- Example app seed from a fixture file:
examples/full_demo/src/sales_data.rs - Sales fixture used by the seed:
examples/full_demo/mocks/collections/sales_database.json - Test-style query examples reproduced as logs:
examples/full_demo/src/queries.rs
โ ๏ธ 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.