Quickstart¶
A 5-minute walkthrough from zero to generated code.
1. Create a Schema¶
Create sql/schema.sql:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
status TEXT NOT NULL DEFAULT 'active'
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
total NUMERIC(10, 2) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
2. Write Annotated Queries¶
Create sql/queries.sql:
-- @name GetUser
-- @returns :one
SELECT id, name, email, status
FROM users
WHERE id = $1;
-- @name ListUsers
-- @returns :many
SELECT id, name, email, status
FROM users
WHERE status = $1
ORDER BY name;
-- @name CreateUser
-- @returns :exec
INSERT INTO users (name, email)
VALUES ($1, $2);
3. Create scythe.toml¶
[scythe]
version = "1"
[[sql]]
name = "main"
engine = "postgresql"
schema = ["sql/schema.sql"]
queries = ["sql/queries.sql"]
output = "src/generated"
4. Generate Code¶
Output:
[main] Parsing schema...
[main] Analyzing 3 queries...
[main] Writing output to src/generated/queries.rs
Done.
5. Generated Code (Rust)¶
src/generated/queries.rs:
// Auto-generated by scythe. Do not edit.
#[derive(Debug, sqlx::FromRow)]
pub struct GetUserRow {
pub id: i32,
pub name: String,
pub email: String,
pub status: String,
}
pub async fn get_user(pool: &sqlx::PgPool, id: i32) -> Result<GetUserRow, sqlx::Error> {
sqlx::query_as!(GetUserRow, "SELECT id, name, email, status FROM users WHERE id = $1", id)
.fetch_one(pool)
.await
}
#[derive(Debug, sqlx::FromRow)]
pub struct ListUsersRow {
pub id: i32,
pub name: String,
pub email: String,
pub status: String,
}
pub async fn list_users(pool: &sqlx::PgPool, status: String) -> Result<Vec<ListUsersRow>, sqlx::Error> {
sqlx::query_as!(ListUsersRow, "SELECT id, name, email, status FROM users WHERE status = $1 ORDER BY name", status)
.fetch_all(pool)
.await
}
pub async fn create_user(pool: &sqlx::PgPool, name: String, email: String) -> Result<(), sqlx::Error> {
sqlx::query!("INSERT INTO users (name, email) VALUES ($1, $2)", name, email)
.execute(pool)
.await?;
Ok(())
}
6. Validate and Lint¶
# Validate SQL parses and types resolve correctly
scythe check
# Lint SQL for correctness, performance, and style
scythe lint
Next Steps¶
- Configuration -- full scythe.toml reference
- Annotations -- all 7 annotation types
- Type Inference -- how nullability analysis works
- Linting -- 22 built-in rules plus sqruff integration