Crate squeal

Crate squeal 

Source
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 queries
  • I(table) - Build INSERT statements
  • U(table) - Build UPDATE statements
  • D(table) - Build DELETE statements
  • T(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", &param))
    .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§

queries

Structs§

CaseExpression
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.
QueryBuilder
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.
WhenThen

Enums§

Distinct
FromSource
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.
JoinType
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.
OrderedColumn
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