Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Expression Builders

QAIL provides ergonomic builder functions for constructing AST expressions without verbose struct creation.

Import

#![allow(unused)]
fn main() {
use qail_core::ast::builders::*;
}

Column References

#![allow(unused)]
fn main() {
// Named column
col("phone_number")

// Star (*)
star()
}

Aggregate Functions

#![allow(unused)]
fn main() {
// COUNT(*)
count()

// COUNT(DISTINCT column)
count_distinct("phone_number")

// COUNT(*) FILTER (WHERE ...)
count_filter(vec![
    eq("direction", "outbound"),
    gt("created_at", now_minus("24 hours")),
]).alias("messages_sent_24h")

// Other aggregates
sum("amount")
avg("score")
min("price")
max("quantity")
}

All aggregates support:

  • .distinct() — Add DISTINCT modifier
  • .filter(conditions) — Add FILTER clause
  • .alias("name") — Add AS alias

Time Functions

#![allow(unused)]
fn main() {
// NOW()
now()

// INTERVAL 'duration'
interval("24 hours")

// NOW() - INTERVAL 'duration' (common pattern)
now_minus("24 hours")

// NOW() + INTERVAL 'duration'
now_plus("7 days")
}

Type Casting

#![allow(unused)]
fn main() {
// expr::type
cast(col("amount"), "float8")

// With alias
cast(col("amount"), "float8").alias("amount_f")
}

CASE WHEN Expressions

#![allow(unused)]
fn main() {
// Simple CASE
case_when(gt("score", 80), text("A"))
    .otherwise(text("F"))
    .alias("grade")

// Multiple WHEN clauses
case_when(gt("score", 90), text("A"))
    .when(gt("score", 80), text("B"))
    .when(gt("score", 70), text("C"))
    .otherwise(text("F"))
    .alias("grade")
}

Condition Helpers

#![allow(unused)]
fn main() {
// Equality
eq("status", "active")      // status = 'active'
ne("status", "deleted")     // status != 'deleted'

// Comparisons
gt("created_at", now_minus("24 hours"))   // created_at > NOW() - INTERVAL '24 hours'
gte("age", 18)              // age >= 18
lt("price", 100)            // price < 100
lte("quantity", 10)         // quantity <= 10

// IN / NOT IN
is_in("status", ["delivered", "read"])    // status IN ('delivered', 'read')
not_in("type", ["spam", "junk"])          // type NOT IN ('spam', 'junk')

// NULL checks
is_null("deleted_at")       // deleted_at IS NULL
is_not_null("email")        // email IS NOT NULL

// Pattern matching
like("name", "John%")       // name LIKE 'John%'
ilike("email", "%@gmail%")  // email ILIKE '%@gmail%'
}

Function Calls

#![allow(unused)]
fn main() {
// Generic function
func("MY_FUNC", vec![col("a"), col("b")])

// COALESCE
coalesce(vec![col("nickname"), col("name"), text("Anonymous")])

// NULLIF
nullif(col("value"), int(0))
}

Binary Expressions

#![allow(unused)]
fn main() {
// Arithmetic
binary(col("price"), BinaryOp::Mul, col("quantity"))

// With alias
binary(
    cast(col("success"), "float8"),
    BinaryOp::Div,
    cast(col("total"), "float8")
).alias("success_rate")
}

Literals

#![allow(unused)]
fn main() {
int(42)           // Integer literal
float(3.14)       // Float literal
text("hello")     // String literal (quoted)
}

Complete Example

Here’s a complex analytics query using all the builders:

#![allow(unused)]
fn main() {
use qail_core::ast::builders::*;

let stats = Qail::get("whatsapp_messages")
    .columns([
        count_distinct("phone_number").alias("total_contacts"),
        count().alias("total_messages"),
        count_filter(vec![
            eq("direction", "outbound"),
            gt("created_at", now_minus("24 hours")),
        ]).alias("messages_sent_24h"),
        count_filter(vec![
            eq("direction", "inbound"),
            eq("status", "received"),
        ]).alias("unread_messages"),
    ]);

let cmd = Qail::get("stats")
    .with_cte("stats", stats)
    .columns([
        col("total_contacts"),
        col("total_messages"),
        case_when(gt("messages_sent_24h", 0),
            binary(
                cast(col("successful"), "float8"),
                BinaryOp::Div,
                cast(col("messages_sent_24h"), "float8")
            )
        ).otherwise(float(0.0)).alias("delivery_rate"),
    ]);
}

This replaces 40+ lines of raw SQL with type-safe, compile-time checked Rust code.