jed

Opening a database

A jed database is a single file on disk. Open or create one, run SQL against it, and commit when you’re done. Pass a path for a durable file, or open a transient in-memory database for tests and scratch work.

Opening or creating returns a Database — the handle you run SQL through. Its execute, query, executeScript, and the update / view transaction helpers each run on a fresh session and commit it, so a bare statement autocommits. For durable per-connection state — a transaction spanning several calls, session variables, or a configured/untrusted caller — mint a separate session from the same handle (see Authorization and Resource limits).

Use the language selector in the top bar to switch this example between Rust, Go, and TypeScript.

use jed::{CreateOptions, Database};

fn main() -> jed::Result<()> {
    // Open a database. `create`/`open` return a `Database` — the handle you run SQL through. A path
    // gives a single-file database on disk; `Database::create(CreateOptions::default())` (no path) is a transient in-memory one.
    // Each bare `execute` autocommits durably (it runs on a fresh session); for a multi-statement
    // transaction use `db.update(...)` or mint a `Session`.
    let mut db = Database::create(CreateOptions { path: Some("people.jed".into()), ..Default::default() })?;

    db.execute("CREATE TABLE person (id i32 PRIMARY KEY, name text NOT NULL)", &[])?;
    db.execute("INSERT INTO person VALUES (1, 'Ada'), (2, 'Grace')", &[])?;

    // query() returns a row cursor; execute() is for statements that produce no rows.
    for row in db.query("SELECT name FROM person ORDER BY id", &[])? {
        println!("{}", row[0].render());
    }

    Ok(())
}

Durability

A bare execute autocommits durably: it runs on a fresh session that commits before the call returns, so the new state is on disk (an in-memory database has nothing to flush). To apply several statements atomically, run them in one update closure — or on a single session’s explicit begin / commit block, where a rollback (or dropping the session) discards the uncommitted work.

In-memory databases

Every example on the SQL pages of these docs runs against an in-memory database, right in your browser — the same engine, no file. Create one by calling the unified create constructor with no path: Database::create(CreateOptions::default()) (Rust), jed.CreateDatabase(jed.CreateOptions{}) (Go), or createDatabase({}) (TypeScript).

Running untrusted queries

jed is built to evaluate untrusted, user-supplied SQL safely: a query — even a hostile one — cannot reach outside the database, corrupt memory, or exhaust resources. The built-in function surface is pure (no filesystem, network, process, or clock access beyond a host-injected seam), and three limits bound the work any one statement can do. Two are caller-set per-session settings you configure on the session that serves untrusted queries — pass them when you mint it, or set them on the session:

  • Cost ceiling — set_max_cost(limit) / SetMaxCost / setMaxCost. Bounds the deterministic execution cost; a query that reaches the ceiling aborts with 54P01. 0 (the default) is unlimited.
  • Input size — set_max_sql_length(bytes) / SetMaxSQLLength / setMaxSqlLength. Bounds the input SQL length (in bytes), rejecting an over-long statement with 54000 before it is parsed — so a giant query can’t exhaust parse memory. The default is 1 MiB; 0 is unlimited. Because jed parses one statement per call, this also bounds the parse tree’s size (a million-column SELECT is just bytes).

Three further limits are fixed engine constants (no configuration): a statement may not nest expressions/subqueries more than 256 deep (54001), a single identifier may not exceed 63 bytes (42622), and a composite type may not nest more than 32 composites deep (54001 at CREATE TYPE — a chain of small CREATE TYPEs that the input-size cap can’t see). Each limit is deterministic and identical across the Rust, Go, and TypeScript cores.

jed — an embeddable, strictly-typed SQL database.