Expand description
§squeal - Type-Safe SQL Query Builder for PostgreSQL
squeal is a lightweight, type-safe SQL query builder for Rust that targets PostgreSQL. It provides both fluent builder APIs and direct struct construction for maximum flexibility.
§Philosophy
- Keep it simple - No macros, no magic, just Rust types
- Type-safe - Catch errors at compile time, not runtime
- Escape hatches - Custom operators and raw SQL when you need them
- Zero overhead - Queries are built at compile time
§Quick Start
use squeal::*;
// Build a SELECT query with the fluent API
let mut qb = Q();
let query = qb.select(vec!["id", "name", "email"])
.from("users")
.where_(eq("active", "true"))
.order_by(vec![OrderedColumn::Desc("created_at")])
.limit(10)
.build();
assert_eq!(
query.sql(),
"SELECT id, name, email FROM users WHERE active = true ORDER BY created_at DESC LIMIT 10"
);§Core Concepts
§Fluent Builders
All query types have builder functions with short, memorable names:
Q()- Build SELECT queriesI(table)- Build INSERT statementsU(table)- Build UPDATE statementsD(table)- Build DELETE statementsT(table)- Build CREATE/DROP TABLE DDL
§Terms and Conditions
The Term enum represents WHERE clause conditions. Use helper functions
for common patterns:
use squeal::*;
// Simple equality: active = true
let simple = eq("active", "true");
// Complex conditions with AND/OR
let complex = Term::Condition(
Box::new(eq("status", "'published'")),
Op::And,
Box::new(gt("views", "1000"))
);
let mut qb = Q();
let query = qb.select(vec!["title", "views"])
.from("posts")
.where_(complex)
.build();
assert_eq!(
query.sql(),
"SELECT title, views FROM posts WHERE status = 'published' AND views > 1000"
);§Advanced Features
§JOINs
All standard JOIN types are supported:
use squeal::*;
let mut qb = Q();
let query = qb.select(vec!["users.name", "COUNT(orders.id) as order_count"])
.from("users")
.left_join("orders", eq("users.id", "orders.user_id"))
.group_by(vec!["users.id", "users.name"])
.order_by(vec![OrderedColumn::Desc("order_count")])
.build();
assert_eq!(
query.sql(),
"SELECT users.name, COUNT(orders.id) as order_count FROM users LEFT JOIN orders ON users.id = orders.user_id GROUP BY users.id, users.name ORDER BY order_count DESC"
);§Common Table Expressions (WITH)
Build complex queries with CTEs for better readability:
use squeal::*;
// Define a CTE to find high-value customers
let mut qb1 = Q();
let high_value = qb1.select(vec!["user_id", "SUM(total) as lifetime_value"])
.from("orders")
.group_by(vec!["user_id"])
.having(gt("SUM(total)", "1000"))
.build();
// Use the CTE in the main query
let mut qb2 = Q();
let query = qb2.with("high_value_customers", high_value)
.select(vec!["users.name", "hvc.lifetime_value"])
.from("users")
.inner_join("high_value_customers hvc", eq("users.id", "hvc.user_id"))
.order_by(vec![OrderedColumn::Desc("hvc.lifetime_value")])
.build();
assert_eq!(
query.sql(),
"WITH high_value_customers AS (SELECT user_id, SUM(total) as lifetime_value FROM orders GROUP BY user_id HAVING SUM(total) > 1000) SELECT users.name, hvc.lifetime_value FROM users INNER JOIN high_value_customers hvc ON users.id = hvc.user_id ORDER BY hvc.lifetime_value DESC"
);§UPSERT (INSERT … ON CONFLICT)
Handle unique constraint violations gracefully:
use squeal::*;
// Insert or update user login count
let mut ib = I("users");
let upsert = ib.columns(vec!["email", "name", "login_count"])
.values(vec!["'alice@example.com'", "'Alice'", "'1'"])
.on_conflict_do_update(
vec!["email"],
vec![
("login_count", "users.login_count + 1"),
("last_login", "NOW()")
]
)
.returning(Columns::Selected(vec!["id", "login_count"]))
.build();
assert_eq!(
upsert.sql(),
"INSERT INTO users (email, name, login_count) VALUES ('alice@example.com', 'Alice', '1') ON CONFLICT (email) DO UPDATE SET login_count = users.login_count + 1, last_login = NOW() RETURNING id, login_count"
);§RETURNING Clauses
Get auto-generated values and track modifications:
use squeal::*;
// Get the ID of newly inserted row
let mut ib = I("posts");
let insert = ib.columns(vec!["title", "content"])
.values(vec!["'Hello World'", "'My first post'"])
.returning(Columns::Selected(vec!["id", "created_at"]))
.build();
assert_eq!(
insert.sql(),
"INSERT INTO posts (title, content) VALUES ('Hello World', 'My first post') RETURNING id, created_at"
);
// Track what was deleted
let mut db = D("old_logs");
let delete = db.where_(lt("created_at", "NOW() - INTERVAL '90 days'"))
.returning(Columns::Selected(vec!["id", "created_at"]))
.build();
assert_eq!(
delete.sql(),
"DELETE FROM old_logs WHERE created_at < NOW() - INTERVAL '90 days' RETURNING id, created_at"
);§Parameterized Queries
Use the Parameterized trait for prepared statements:
use squeal::*;
let mut qb = Q();
let param = qb.param();
let query = qb.select(vec!["*"])
.from("users")
.where_(eq("email", ¶m))
.build();
assert_eq!(query.sql(), "SELECT * FROM users WHERE email = $1");§More Information
See the README for complete documentation and examples.
Re-exports§
pub use queries::create_table::CreateTable;pub use queries::create_table::T;pub use queries::create_table::TableBuilder;pub use queries::delete::D;pub use queries::delete::Delete;pub use queries::delete::DeleteBuilder;pub use queries::drop_table::DropTable;pub use queries::insert::I;pub use queries::insert::Insert;pub use queries::insert::InsertBuilder;pub use queries::insert::InsertSource;pub use queries::insert::OnConflict;pub use queries::select::Columns;pub use queries::select::Select;pub use queries::select::SelectExpression;pub use queries::update::U;pub use queries::update::Update;pub use queries::update::UpdateBuilder;
Modules§
Structs§
- Case
Expression - Cte
- Represents a Common Table Expression (CTE) in a WITH clause
- Having
- The Having struct is used to specify the having clause in a query. It is used in the Query struct.
- Join
- Represents a JOIN clause in a SQL query
- OrderBy
- The OrderBy struct is used to specify the order by clause in a query. It is used in the Query struct. It is used to specify the columns, and optionally, whether they are ascending or descending. Each column can be ascending or descending
- PgParams
- PostgreSQL parameter counter for auto-sequencing Automatically generates $1, $2, $3, etc. as you call seq()
- Query
- The Query struct is the top-level object that represents a query. The user is expected to construct the Query object and then call the sql() method to get the SQL string.
- Query
Builder - The QueryBuilder struct is a fluent interface for building a Query. It is not intended to be used directly, but rather through the Q() function. See the integration_test.rs for an example of usage.
- When
Then
Enums§
- Distinct
- From
Source - The FromSource enum represents the source of data in a FROM clause. It can be either a simple table name or a subquery with an alias.
- Join
Type - Join type for SQL JOIN clauses
- Op
- The Op enum is used to specify the operator in a condition. It is used in the Term struct.
- Ordered
Column - The OrderedColumn enum is used to specify the order by clause in a query. It is used in the OrderBy struct. It is used to specify the columns, and optionally, whether they are ascending or descending.
- Term
- The Term enum is used to specify a condition in a query (WHERE clause). It is used in the Query struct.
Traits§
- Build
- The Build trait is used by the XBuilder structs to build the X struct. This is a means of providing a nice factory/fluent interface.
- Parameterized
- The Parameterized trait provides PostgreSQL parameter placeholder generation. Implemented by all query builder structs to provide consistent param() API.
- Sql
- The Sql trait is implemented by all objects that can be used in a query. It provides a single method, sql(), that returns a String.
Functions§
- Q
- The Q function is a fluent interface for building a Query. The user is expected to construct the Query object and then call the sql() method to get the SQL string. The goal is any valid construction of a QueryBuilder is a valid Query and will, at least, syntactically, be valid SQL.
- all
- Creates a comparison with ALL (subquery) Example: all(“price”, Op::LessThan, subquery) => “price < ALL (SELECT …)”
- and
- Combines two terms with AND
- any
- Creates a comparison with ANY (subquery) Example: any(“price”, Op::GreaterThan, subquery) => “price > ANY (SELECT …)”
- between
- Creates a BETWEEN clause Example: between(“age”, “18”, “65”) => “age BETWEEN 18 AND 65”
- case
- Creates a CASE expression
- cast
- Creates a CAST expression
- coalesce
- Creates a COALESCE expression
- concat
- Creates a CONCAT expression
- current_
date - Creates a CURRENT_DATE expression
- date_
add - Creates a date addition expression
- date_
sub - Creates a date subtraction expression
- eq
- Creates an equality condition (=)
- exists
- Creates an EXISTS condition with a subquery Example: exists(subquery) => “EXISTS (SELECT …)”
- gt
- Creates a greater-than condition (>)
- gte
- Creates a greater-than-or-equal condition (>=)
- in_
- Creates an IN clause Example: in_(“status”, vec![“‘active’”, “‘pending’”]) => “status IN (‘active’, ‘pending’)”
- in_
subquery - Creates an IN condition with a subquery Example: in_subquery(“user_id”, subquery) => “user_id IN (SELECT …)”
- interval
- Creates an INTERVAL expression
- is_
not_ null - Creates an IS NOT NULL condition Example: is_not_null(“created_at”) => “created_at IS NOT NULL”
- is_null
- Creates an IS NULL condition Example: is_null(“deleted_at”) => “deleted_at IS NULL”
- like
- Creates a LIKE condition
- lower
- Creates a LOWER expression
- lt
- Creates a less-than condition (<)
- lte
- Creates a less-than-or-equal condition (<=)
- ne
- Creates a not-equals condition (!=)
- not
- Negates a term with NOT
- not_
exists - Creates a NOT EXISTS condition with a subquery Example: not_exists(subquery) => “NOT EXISTS (SELECT …)”
- now
- Creates a NOW() expression
- nullif
- Creates a NULLIF expression
- or
- Combines two terms with OR
- p
- Returns a PostgreSQL parameter placeholder Example: p(1) => “$1”, p(2) => “$2”
- parens
- Wraps a term in parentheses
- pg_cast
- Creates a PostgreSQL-style CAST expression (::)
- substring
- Creates a SUBSTRING expression
- upper
- Creates a UPPER expression