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.
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;
String is NOT NULL, String? is nullable, and ?. through a reference is a LEFT JOIN. The type of the result says which join happened.
Bool3 appears only when an operand is nullable. The compiler tells you where a == can be unknown — and === treats null as a value.
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.
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.
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.
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.
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.
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