SQLite bindings for Mojo with a safe API, Pythonic context-manager transactions, and an ORM layer powered by compile-time reflection via morph.
- Three-layer design: raw FFI, safe
Database/Statement/RowAPI, andmorph-based ORM - Pythonic transactions:
with db.transaction():commits on success and rolls back automatically on exception (identical to Python'swith conn:) - ORM:
create_table,insert,querydriven by compile-time struct reflection; no SQL to write for basic CRUD - Rich type support:
String,Int,Int64,Float64,Float32,Bool,Optional[T] - Thoroughly tested: unit, edge-case, and property-based fuzz tests via mozz
from sqlite import Database, create_table, insert, query
@fieldwise_init
struct Person(Defaultable, Movable):
var name: String
var age: Int
var score: Float64
def __init__(out self):
self.name = ""
self.age = 0
self.score = 0.0
def main() raises:
var db = Database(":memory:")
create_table[Person](db, "people")
insert[Person](db, "people", Person(name="Alice", age=30, score=9.5))
insert[Person](db, "people", Person(name="Bob", age=25, score=7.2))
var rows = query[Person](db, "people")
for i in range(len(rows)):
print(rows[i].name, rows[i].age, rows[i].score)
# Alice 30 9.5
# Bob 25 7.2db.transaction() supports Mojo's with statement, giving you the same
auto-commit / auto-rollback semantics as Python's with conn:.
from sqlite import Database
def transfer(mut db: Database, from_id: Int, to_id: Int, amount: Int) raises:
with db.transaction():
db.execute(
"UPDATE accounts SET balance = balance - "
+ String(amount) + " WHERE id = " + String(from_id)
)
db.execute(
"UPDATE accounts SET balance = balance + "
+ String(amount) + " WHERE id = " + String(to_id)
)
# -> COMMIT on success; ROLLBACK + re-raise if either UPDATE raisedFor explicit guard access (e.g., conditional rollback without raising), use
the var tx form. Mojo's with/__exit__ protocol requires a non-consuming
__enter__, so with ... as tx: would bind tx to None:
var tx = db.transaction() # BEGIN
db.execute("INSERT ...")
if some_condition:
tx.rollback() # abort without raising
return
tx.commit()var tx = db.transaction() # BEGIN
db.execute("INSERT ...")
tx.commit() # explicit COMMIT
# Abandon without raising: immediate ROLLBACK
var tx2 = db.transaction()
db.execute("INSERT ...")
_ = tx2^ # consume guard -> ROLLBACK right herefrom sqlite import Database
def main() raises:
var db = Database(":memory:")
db.execute("CREATE TABLE t (id INTEGER, label TEXT)")
var stmt = db.prepare("INSERT INTO t VALUES (?, ?)")
stmt.bind_int(1, 42)
stmt.bind_text(2, "hello")
_ = stmt.step()
var q = db.prepare("SELECT id, label FROM t")
while True:
var row = q.step()
if not row:
break
print(row.value().int_val(0), row.value().text_val(1))
# 42 helloAdd sqlite to your project's pixi.toml:
[workspace]
channels = ["https://conda.modular.com/max-nightly", "conda-forge"]
preview = ["pixi-build"]
[dependencies]
sqlite = { git = "https://github.com/ehsanmok/sqlite.git", tag = "v0.1.1" }Then run:
pixi installFor the latest development version:
[dependencies]
sqlite = { git = "https://github.com/ehsanmok/sqlite.git", branch = "main" }Progressive examples live in examples/:
| File | What it shows |
|---|---|
01_hello_sqlite.mojo |
Open a database, CREATE TABLE, INSERT, SELECT |
02_prepared_statements.mojo |
Bind parameters, iterate rows, reuse statements |
03_all_types.mojo |
Every supported column type round-trip |
04_orm_basics.mojo |
ORM create_table / insert / query |
05_orm_optional.mojo |
Optional fields, WHERE / ORDER BY |
06_contacts_app.mojo |
Realistic CRUD mini-app with transactions |
07_transactions.mojo |
Bank-transfer demo: with, as tx, manual, _ = tx^ |
Full API reference: ehsanmok.github.io/sqlite
pixi run tests # run all tests (db + ORM + fuzz)
pixi run test-db # db layer only
pixi run test-orm # ORM layer only
pixi run test-fuzz # property-based fuzz tests
pixi run examples # run all examples
pixi run example-07 # transactions example
pixi run bench # micro-benchmarks
pixi run -e dev docs # build and open API docs
pixi run format # auto-format source