Queries in .sql files¶
schema.sql:
sqlty check reads the named queries of the .sql files queries matches, and
runs the same rules on them as on queries in Python strings. Projects that keep
SQL in files, loaded with aiosql,
PugSQL or shared with sqlc,
get their misspelled columns and unknown tables reported:
-- name: get-user-by-email^
-- error: [unknown-column] "column `emial` does not exist in `users`; did you mean `email`?"
SELECT id, name, emial FROM users WHERE email = :email;
queries is kept apart from schema, so a migration in schema is never read as a query. It
takes files, directories (every *.sql under them) and globs, relative to the
configuration file, as schema does. sqlty check sql/ checks the query files
under sql/; a .sql file queries does not match is not checked.
Headers¶
Each file's first header picks its format; query-format fixes one instead. A
query runs from the line after its header to the next header. Text before the
first header is no query.
query-format |
Header | Query name |
|---|---|---|
aiosql |
-- name: get-user^ |
get_user: - reads as _ |
sqlc |
-- name: GetUser :one |
GetUser |
pugsql |
-- :name get_user :one |
get_user |
file |
none | the file name: one query per file, as Squirrel and Prisma TypedSQL keep them |
auto (default) |
the first header found |
A query of several statements, such as an aiosql script (-- name: reset#),
has each statement checked. :name and $1 are parameters, as in Python
strings, and so are the placeholders of each format:
| Format | Placeholder | Parameter |
|---|---|---|
sqlc |
@name, sqlc.arg(name), sqlc.narg(name) |
name |
aiosql |
:user.email |
attribute email of user |
Placeholders inside strings and comments are left alone.
-- name: GetUser :one
-- error: [unknown-column] "column `nme` does not exist in `users`; did you mean `name`?"
SELECT id, nme FROM users WHERE id = $1;
What is checked¶
The name rules (unknown-table,
unknown-column, ambiguous-column, ...),
unparsed-query and
select-star, with the severities
[tool.sqlty.rules] and overrides give them. A file has no row types, so the
rules that compare one with the query don't apply.
-- name: list-users
-- error: [select-star] "SELECT * used — expand to explicit column list"
SELECT * FROM users;
sqlty check --fix does not expand the star in a .sql file: list the columns
by hand.
-- sqlty: ignore and -- sqlty: ignore[rule] silence the diagnostics of their
line, and unused-ignore reports one that silences nothing:
--output-format json, github and sarif report them like any other
diagnostic, with the line and column in the .sql file.
Inferred types¶
sqlty check -v prints the parameters and row type of each named query, as
SQL[R, P] would spell them in Python:
sql/users.sql: get_user_by_email(email: str) -> tuple[int, str, str]
sql/users.sql: delete_user($1: int) -> None
None is a query without rows, and unknown one whose columns are not all
typed: a diagnostic says why. A query of several statements is typed by its
first.
sqlty breaking compares the query files of both revisions, as it compares the
Python files: see Breaking schema changes.
sqlty server publishes the diagnostics of a .sql buffer that queries names,
as you type. The VS Code extension sends it sql files; in Neovim, add "sql"
to the client's filetypes.