IVF with dense vectors (clustered)
August 11, 2026 · View on GitHub
MongrelDB Mojo Client
Pure Mojo client for MongrelDB - embedded+server database with SQL, vector search, full-text search, and AI-native retrieval.
No external packages required - built on Python's standard-library urllib via Mojo's seamless Python interop. The API mirrors the MongrelDB Python, Ruby, and Go clients.
Package
| Surface | Package | Install |
|---|---|---|
| Mojo client | mongreldb | not published to a registry - use from source (below) |
Requirements
- Mojo 0.25.7 (the Modular toolchain)
- A running
mongreldb-serverdaemon
What It Provides
- Typed CRUD over the Kit transaction endpoint:
put,upsert(insert-or-update on PK conflict),deleteby row id or primary key, all with optional idempotency keys for safe retries. - Fluent query builder that pushes conditions down to the engine's specialized indexes for sub-millisecond lookups: bitmap equality/IN, learned-range, null checks, FM-index full-text search, HNSW vector similarity (
ann), and sparse vector match. Friendly aliases (column->column_id,min/max->lo/hi) are translated to the server's on-wire keys. - Idempotent batch transactions - operations staged locally and committed atomically, with the engine enforcing unique, foreign-key, and check constraints at commit time.
- Full SQL access through the DataFusion-backed
/sqlendpoint. JSON mode (sql) decodes row arrays;sql_arrowrequests raw Arrow IPC bytes (format: "arrow"). - Schema management: typed table creation, full schema catalog, and per-table descriptors. Column dictionaries preserve scalar
default_valueand dynamicdefault_expr("now"or"uuid"). - User/role/credentials management via SQL: Argon2id-hashed catalog users, roles, and
GRANT/REVOKEtable-level permissions, all executed throughsql. - Maintenance: compaction (all tables or per-table).
- Auth: Bearer token (
--auth-tokenmode) and HTTP Basic (--auth-usersmode), with the bearer token taking precedence. Credentials are CRLF-validated to prevent header injection. - Typed error categories:
MongrelDBError(base),AuthError(401/403),NotFoundError(404),ConflictError(409, with code + op index), andQueryError(everything else, including network failures) - raised asErrorvalues whose message carries the category prefix. - Response size limit (256 MB) to guard client memory against a malicious or buggy server.
How it works
Mojo is a Python superset with seamless interop. This client calls Python's
standard-library urllib.request, json, and base64 via Python.import_module
- no third-party packages and no C FFI. All request/response logic (auth, error mapping, cell flattening, alias translation) is written in Mojo.
Install
The package is not published to a registry yet, so clone this repo and use it from source:
git clone https://github.com/visorcraft/MongrelDB-Mojo.git
cd MongrelDB-Mojo
mojo run -I src examples/basic_crud.mojo
The client package lives under src/mongreldb/; run your own programs against
the checkout the same way, with mojo run -I src.
Examples
Task-focused, commented guides live in docs/:
- Quickstart - install, start the daemon, write and run a complete program.
- Transactions - batch commits, idempotency keys, constraint handling.
- Queries - every native condition type and the index it pushes down to.
- SQL - recursive CTEs, window functions, advanced SQL.
- Authentication - Bearer token, HTTP Basic, and open modes.
- Errors - the error categories and recovery patterns.
Quick Example
from python import Python, PythonObject
from collections import Optional
from mongreldb import MongrelDB
fn main() raises:
var db = MongrelDB("http://127.0.0.1:8453")
# Create a table. Column ids are stable on-wire identifiers.
var columns = Python.list()
columns.append(col(1, "id", "int64", primary_key=True))
columns.append(col(2, "customer", "varchar"))
columns.append(col(3, "amount", "float64"))
var check = Python.dict()
check["id"] = 1
check["name"] = "id_present"
var expr = Python.dict()
expr["IsNotNull"] = 1
check["expr"] = expr
var constraints = Python.dict()
var checks = Python.list()
checks.append(check)
constraints["checks"] = checks
_ = db.create_table("orders", columns, constraints)
_ = db.put("orders", cells3(1, 1, 2, "Alice", 3, 99.50))
_ = db.put("orders", cells3(1, 2, 2, "Bob", 3, 150.00))
var params = Python.dict()
params["column"] = 3
params["min"] = 100.0
params["max"] = 200.0
params["min_inclusive"] = True
params["max_inclusive"] = True
var q = db.query("orders").where("range_f64", params).limit(100)
var rows = q.execute()
print(db.count("orders")) # 2
_ = db.sql("UPDATE orders SET amount = 200.0 WHERE customer = 'Bob'")
def col(
col_id: Int,
name: String,
ty: String,
*,
primary_key: Bool = False,
default_value: Optional[PythonObject] = None,
) -> PythonObject:
c = Python.dict()
c["id"] = col_id
c["name"] = name
c["ty"] = ty
c["primary_key"] = primary_key
c["nullable"] = False
if default_value:
c["default_value"] = default_value.value()
return c
def cells3(
k1: PythonObject, v1: PythonObject,
k2: PythonObject, v2: PythonObject,
k3: PythonObject, v3: PythonObject,
) -> PythonObject:
d = Python.dict()
d[k1] = v1
d[k2] = v2
d[k3] = v3
return d
Authentication
# Bearer token (--auth-token mode)
var db1 = MongrelDB("http://127.0.0.1:8453", "my-secret-token")
# HTTP Basic (--auth-users mode)
var db2 = MongrelDB("http://127.0.0.1:8453", "", "admin", "s3cret")
# Defaults: daemon address 127.0.0.1:8453, no auth.
var db3 = MongrelDB()
Batch transactions
var txn = db.begin()
_ = txn.put("orders", cells3(1, 10, 2, "Dave", 3, 50.0))
_ = txn.put("orders", cells3(1, 11, 2, "Eve", 3, 75.0))
_ = txn.delete_by_pk("orders", 2)
try:
var results = txn.commit() # atomic - all or nothing
except e:
if String(e).contains("ConflictError"):
print("constraint violated")
SQL
db.sql("INSERT INTO orders (id, customer, amount) VALUES (99, 'Zoe', 999.0)")
db.sql("CREATE TABLE archive AS SELECT * FROM orders WHERE amount > 500")
ANN index backends
The engine's ann index is swappable across three backends - hnsw (the default), diskann, and ivf - selected with the algorithm option. Quantization is independently configurable: dense, binary_sign, or product (product quantization, with num_subvectors, bits_per_subvector, pq_training_samples, pq_seed, and pq_rerank_factor). These are ordinary DDL strings run through sql, so no client changes are needed.
# DiskANN (in-memory Vamana graph)
db.sql("CREATE INDEX orders_emb_diskann ON orders USING ann (embedding) WITH (algorithm = 'diskann', quantization = 'dense', diskann_l = 50, diskann_r = 64, beam_width = 8)")
# IVF with dense vectors (clustered)
db.sql("CREATE INDEX orders_emb_ivf ON orders USING ann (embedding) WITH (algorithm = 'ivf', quantization = 'dense', nlist = 1024, nprobe = 16)")
# HNSW with product quantization (recall-tuned)
db.sql("CREATE INDEX orders_emb_hnsw_pq ON orders USING ann (embedding) WITH (algorithm = 'hnsw', quantization = 'product', m = 16, ef_construction = 200, ef_search = 50, num_subvectors = 32, pq_training_samples = 50000, pq_rerank_factor = 8)")
History retention
Control how far back time-travel queries can read. The window is measured in epochs (monotonically increasing commit numbers).
# Keep at least 1000 epochs of history readable.
var result = db.set_history_retention_epochs(1000)
print(result["history_retention_epochs"]) # 1000
print(result["earliest_retained_epoch"]) # oldest epoch still available
print(db.history_retention_epochs()) # 1000
print(db.earliest_retained_epoch()) # oldest readable epoch
# Read a table as it existed at a specific epoch.
var rows = db.sql("SELECT label FROM orders AS OF EPOCH 42 WHERE id = 1")
Raising retention prevents history from being garbage collected, but it cannot restore epochs that have already been pruned. These endpoints require admin privileges when the daemon runs with auth enabled.
Error handling
Every non-2xx response is mapped to a typed error category. Mojo raises only
the built-in Error type, so the category, HTTP status, structured code, and
op index are embedded in the error message - match on the category prefix:
try:
db.put("orders", cells(1, 1))
except e:
var msg = String(e)
if msg.contains("ConflictError"):
print("constraint violated")
elif msg.contains("NotFoundError"):
print("not found")
else:
print("query/server error: " + msg)
API reference
MongrelDB
| Method | Description |
|---|---|
MongrelDB(url, token, username, password) | Construct a client (url defaults to http://127.0.0.1:8453) |
health() -> Bool | Check daemon health |
table_names() -> List[String] | List table names |
create_table(name, columns, constraints, indexes) -> Int | Create a table with optional constraints and all index definitions |
drop_table(name) -> None | Drop a table |
count(table) -> Int | Row count |
put(table, cells, idempotency_key) -> dict | Insert a row |
upsert(table, cells, update_cells, idempotency_key) -> dict | Upsert a row |
delete(table, row_id) -> None | Delete by row id |
delete_by_pk(table, pk) -> None | Delete by primary key |
query(table) -> QueryBuilder | Start a native query |
sql(sql) -> list | Execute SQL (JSON mode) |
sql_arrow(sql) -> Bytes | Execute SQL requesting raw Arrow IPC |
schema() -> dict | Full schema catalog |
schema_for(table) -> dict | Single-table descriptor |
set_history_retention_epochs(epochs) -> dict | Set the history retention window |
history_retention_epochs() -> Int | Get the current retention window |
earliest_retained_epoch() -> Int | Get the oldest readable epoch |
compact() -> dict | Compact all tables |
compact_table(table) -> dict | Compact one table |
begin() -> Transaction | Start a batch |
QueryBuilder
| Method | Description |
|---|---|
where(cond_type, params) -> Self | Add a native condition (AND-ed) |
projection(column_ids) -> Self | Set column projection |
limit(n) -> Self | Set row limit |
offset(n) -> Self | Skip matching rows before the limit |
build() -> dict | Build the request payload |
execute() -> list | Run the query |
truncated() -> Bool | Whether the last execute result hit the limit |
Transaction
| Method | Description |
|---|---|
put(table, cells, returning) -> Self | Stage an insert |
upsert(table, cells, update_cells, returning) -> Self | Stage an upsert |
delete(table, row_id) -> Self | Stage a delete by row id |
delete_by_pk(table, pk) -> Self | Stage a delete by primary key |
count() -> Int | Number of staged operations |
commit(idempotency_key) -> list | Commit atomically |
rollback() -> None | Discard all operations |
Errors
| Category | HTTP status | Notes |
|---|---|---|
MongrelDBError | - | Base category for all client errors |
AuthError | 401, 403 | Bad or missing credentials |
NotFoundError | 404 | Missing table, schema, or resource |
ConflictError | 409 | Constraint violation; carries code and op_index |
QueryError | 400, 5xx, network | Everything else |
Building and testing
The test suite is split into two layers:
- Offline unit tests - query-builder alias translation, URL escaping, error mapping. No daemon needed.
- Live integration tests - boots a real
mongreldb-serverdaemon and exercises the full client surface (the 14-operation conformance matrix). Live tests skip cleanly when no binary is available.
mojo run -I src tests/live_test.mojo # runs the whole suite
Fetch a prebuilt server binary from the MongrelDB releases:
mkdir -p bin
curl -fsSL -o bin/mongreldb-server \
https://github.com/visorcraft/MongrelDB/releases/download/v0.64.16/mongreldb-server-linux-x64
chmod +x bin/mongreldb-server
The live harness resolves the binary in this order: the MONGRELDB_SERVER env
var, ./bin/mongreldb-server, mongreldb-server on PATH. Or point it at an
already-running daemon with MONGRELDB_URL.
Contributing
Contributions are welcome. Please:
- Open an issue first for non-trivial changes.
- Add focused tests near your change - the suite must stay green.
- Run
mojo run -I src tests/live_test.mojobefore submitting. - Keep the client dependency-free (Python standard library only).
Related tools
- Mongrel — Commercial multi-system workbench with native MongrelDB support.
- MongrelDB Viewer — Free, open-source MongrelDB GUI and MCP server.
License
Dual-licensed under the MIT License or the Apache License, Version 2.0, at your option. See MIT OR Apache-2.0 for the full text.
SPDX-License-Identifier: MIT OR Apache-2.0