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(())
}
package main
import (
"context"
"fmt"
"log"
jed "github.com/jackc/jed/impl/go"
)
func main() {
// Open a database. CreateDatabase/OpenDatabase return a *Database — the handle you run SQL
// through. A path gives a single-file database on disk; jed.CreateDatabase(jed.CreateOptions{}) (no path) is a transient
// in-memory one. Each bare Exec autocommits durably (it runs on a fresh session); for a
// multi-statement transaction use db.Update(...) or mint a Session.
db, err := jed.CreateDatabase(jed.CreateOptions{Path: "people.jed"})
if err != nil {
log.Fatal(err)
}
defer db.Close()
ctx := context.Background()
mustExec(db, "CREATE TABLE person (id i32 PRIMARY KEY, name text NOT NULL)")
mustExec(db, "INSERT INTO person VALUES (1, 'Ada'), (2, 'Grace')")
// Query returns a row cursor; Exec is for statements that produce no rows.
rows, err := db.Query(ctx, "SELECT name FROM person ORDER BY id")
if err != nil {
log.Fatal(err)
}
for rows.Next() {
fmt.Println(rows.Row()[0].Render())
}
}
func mustExec(db *jed.Database, sql string) {
if _, err := db.Exec(context.Background(), sql); err != nil {
log.Fatal(err)
}
}
import { createDatabase, render } from 'jed-ts';
// Open a database. createDatabase/openDatabase return a Database — the handle you run SQL through. A
// path gives a single-file database on disk; `createDatabase({})` (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.
const db = createDatabase({ path: 'people.jed' });
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 (const row of db.query('SELECT name FROM person ORDER BY id')) {
console.log(render(row[0]));
}
db.close();
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 with54P01.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 with54000before it is parsed — so a giant query can’t exhaust parse memory. The default is 1 MiB;0is unlimited. Because jed parses one statement per call, this also bounds the parse tree’s size (a million-columnSELECTis 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.