Not a fork — plain SQL for the PostgreSQL you already run

Keal's shape over PostgreSQL.

A schema and its queries in one file, in the syntax of Keal, checked against each other: a column the schema does not have, or a null where it forbids one, is a compile error. The output is SQL an unmodified PostgreSQL runs.

blog.kealsql▸ sql
table User {
    id:      Id
    name:    Slug
    unique email: String
    bio:     String?
}

table Post {
    id:       Id
    cascade author: RefId<User>
    editor:   RefId<User>?
    title:    String
    status:   Status
    created:  Timestamp

    // A method on the row, for the programs that read it.
    func headline(): String { "${this.title} [${this.status}]" }
}

func byAuthor(name: String): List<(Int, String)> {
    from(Post as p)
        .where(p.author.name == name)
        .where(status == Published)
        .orderBy(created.desc)
        .select(p.id, p.title)
}

func editorOf(post: Int): String? {
    from(Post).where(id == post).select(editor?.name).first()
}
-- func editorOf(post: Int): String?
PREPARE editor_of(integer) AS
SELECT editor.name
FROM post
LEFT JOIN "user" AS editor ON post.editor = editor.id
WHERE post.id = $1
LIMIT 1;

Null safety against the schema

String is NOT NULL, String? is nullable, and ?. through a reference is a LEFT JOIN. The type of the result says which join happened.

Three-valued logic, made visible

Bool3 appears only when an operand is nullable. The compiler tells you where a == can be unknown — and === treats null as a value.

Migrations from a diff

The file is the schema. --migrate reads the live catalog and prints what brings it to the file; destructive steps are held back until you say so.

Keal inside the server

stored func and trigger bodies are Keal, compiled through C into LANGUAGE C functions — the fastest kind PostgreSQL has — calling the file's own queries, typed.

The same queries from a program

A Keal program imports the .kealsql itself and gets one typed method per query on a libpq connection. The application never writes SQL, and a renamed column stops it compiling.

One file, three places to run it.

The compiled SQL loads into psql as prepared statements. The client makes them methods in a Keal program. And a stored function calls them from inside the server, through SPI, with the same types in all three.

psqla Keal programinside PostgreSQL

A transpiler, deliberately.

Query performance is a property of the SQL emitted and of PostgreSQL's planner, not of the front-end. The SQL is built at compile time, so every query is a constant prepared statement — exactly what a careful hand would write — and the whole ecosystem, from pg_dump to managed hosting, keeps working.

Running in a minute.

shell
git clone https://github.com/geneacta/kealsql
cd kealsql
keal fetch
tests/run.sh
keal src/main.keal blog.kealsql > blog.sql
createdb blog
psql -d blog -f blog.sql

Then the guide: create a database, use it, evolve it →