Query Predict
Query Predict (qp) is Emi’s approach to hand-written SQL with generated, type-safe Go bindings. You write plain
.sql files; Emi parses them, predicts the shape of each result row and the parameters they need, and generates Go
code with a typed row struct and a prepared-statement builder. There is no schema introspection, no auto-migration and
no SQL synthesis — the SQL you write is the SQL that runs.
This keeps the full power of SQL in your hands while still giving you compile-time safety over the columns and parameters a query touches.
Commands
Section titled “Commands”| Command | Description |
|---|---|
emi qp:dir | Scans a directory for .sql files and generates QueryPredict Go code for each. |
emi qp:gen | Generates the QueryPredict Go files from a Query Predict YAML definition. |
emi qp:sql | Compiles a single query into executable Go and transforms the metadata into pure SQL. |
Generating from a directory of .sql files
Section titled “Generating from a directory of .sql files”Point qp:dir at a directory of .sql files and it generates one Go file per query into the output directory. The
generated file name is derived from the SQL file name.
emi qp:dir --path ./queries-source --output ./outputGenerating from a YAML definition
Section titled “Generating from a YAML definition”qp:gen reads a Query Predict document that bundles several queries together and lets you set the Go package name:
emi qp:gen --path ./queries.yaml --output ./outputpackage: queriesqueries: - name: basicSelect query: select name, id from users; - name: countUsers query: select count(*) as 'count' from usersOptions
Section titled “Options”| Flag | Description |
|---|---|
—path | Path of the directory (qp:dir) or the Query Predict YAML/SQL file. |
—output | Directory the generated Go files are written to. |
—tags | Comma separated compile features to add or remove. |
What gets generated
Section titled “What gets generated”For a query like select name, id from users; Emi emits a typed row struct, the original SQL as a constant, and a
context/prepare helper:
const BasicSelectSQL = `select name, id from users;`
type BasicSelectRow struct { Name string Id string}
type BasicSelectContext struct { Filter string Having string Restriction string Params map[string]interface{} Placeholders []any}
func BasicSelectPrepreSql(ctx BasicSelectContext) (string, error) { // ...substitutes useval(), filter(), restriction() and having into the SQL}SQL helpers
Section titled “SQL helpers”Query Predict understands a small set of marker functions inside your SQL. They let you keep dynamic behaviour in the query while still generating predictable, typed code.
| Helper | Purpose |
|---|---|
field(expr, ‘type’, ‘goName’?) | Annotate a selected expression so Emi knows its Go type (and optionally the Go field name) when it cannot be inferred. |
useval(‘name’) | A named value substituted from ctx.Params at runtime, safely escaped. |
filter() | Placeholder replaced by the Filter clause from the context (defaults to 1). |
restriction() | Placeholder replaced by the Restriction clause from the context (defaults to 1). |
An example combining them:
SELECT u.user_id as user_id, field(u.user_name, 'string') as UserName, field(COUNT(o.order_id), 'int64') AS total_ordersFROM users uLEFT JOIN orders o ON u.user_id = o.user_idWHERE filter()GROUP BY u.user_id, u.user_nameHAVING MAX(o.total) > 100ORDER BY total_orders DESCLIMIT useval('limit')Here field(..., 'int64') tells the generator the aggregate column is an int64, filter() is swapped for whatever
filter the caller supplies, and useval('limit') is replaced by the limit entry of ctx.Params.