Skip to main content

Chain

Trait Chain 

Source
pub trait Chain:
    IntoExpr
    + IntoExprList
    + Sized {
Show 24 methods // Required method fn from_expr(e: Expr) -> Self; // Provided methods fn step(self, f: impl FnOnce(Expr) -> Expr) -> Self { ... } fn op(self, op: &'static str, rhs: impl IntoExpr) -> Self { ... } fn eq(self, rhs: impl IntoExpr) -> Self { ... } fn ne(self, rhs: impl IntoExpr) -> Self { ... } fn lt(self, rhs: impl IntoExpr) -> Self { ... } fn lte(self, rhs: impl IntoExpr) -> Self { ... } fn gt(self, rhs: impl IntoExpr) -> Self { ... } fn gte(self, rhs: impl IntoExpr) -> Self { ... } fn in_(self, vals: impl IntoExprList) -> Self { ... } fn not_in(self, vals: impl IntoExprList) -> Self { ... } fn is_null(self) -> Self { ... } fn is_not_null(self) -> Self { ... } fn is_distinct_from(self, rhs: impl IntoExpr) -> Self { ... } fn is_not_distinct_from(self, rhs: impl IntoExpr) -> Self { ... } fn between(self, a: impl IntoExpr, b: impl IntoExpr) -> Self { ... } fn not_between(self, a: impl IntoExpr, b: impl IntoExpr) -> Self { ... } fn like(self, rhs: impl IntoExpr) -> Self { ... } fn concat(self, others: impl IntoExprList) -> Self { ... } fn and(self, others: impl IntoExprList) -> Self { ... } fn or(self, others: impl IntoExprList) -> Self { ... } fn plus(self, rhs: impl IntoExpr) -> Self { ... } fn minus(self, rhs: impl IntoExpr) -> Self { ... } fn as_(self, alias: impl IntoIdent) -> Expr { ... }
}
Expand description

The operator chain: quote("age").gte(arg(21)).

Every method builds a node around self and then applies the parenthesisation rule (Expr::grouped), which is what bob’s expr.X does at the end of each of its chain methods. Because the rule is idempotent and treats Expr::Group as atomic, a chain value is always “already parenthesised or not needing it”, and successive steps never pile up redundant parentheses.

§How a dialect adds an operator

bob parameterises Chain[T, B] over the dialect’s own expression type so that keelson-psql can add @> without touching core. In Rust the same extensibility comes from a trait with default methods, and there are two ways to take it — neither needs a change to core.

An extension trait, which is the normal case and needs no new type:

use keelson_core::expr::{Chain, Expr, IntoExpr, arg, quote};

// PostgreSQL-only operators, reachable only where this trait is imported.
trait PsqlOps: Chain {
    /// `@>` — contains. Nothing but a symbol, so `op` is the whole story.
    fn contains(self, rhs: impl IntoExpr) -> Self {
        self.op("@>", rhs)
    }

    /// A shape `op` cannot express, built through `step` so that the
    /// parenthesisation rule is still applied for us.
    fn between_symmetric(self, a: impl IntoExpr, b: impl IntoExpr) -> Self {
        self.step(move |lhs| {
            Expr::join((lhs, Expr::raw("BETWEEN SYMMETRIC"), a, Expr::raw("AND"), b))
        })
    }
}

impl<T: Chain> PsqlOps for T {}

let (sql, _) = keelson_core::build(&Psql, &quote("tags").contains(arg("x")))?;
assert_eq!(sql, r#"("tags" @> $1)"#);

A newtype, when the dialect wants its operators to be unreachable from another dialect’s expressions rather than merely un-imported:

use keelson_core::expr::{Chain, Expr, IntoExpr, IntoExprList};

#[derive(Debug, Clone)]
struct PsqlExpr(Expr);

impl IntoExpr for PsqlExpr {
    fn into_expr(self) -> Expr { self.0 }
}

impl IntoExprList for PsqlExpr {
    fn into_expr_list(self) -> Vec<Expr> { vec![self.0] }
}

impl Chain for PsqlExpr {
    fn from_expr(e: Expr) -> Self { PsqlExpr(e) }
}

impl PsqlExpr {
    fn contains(self, rhs: impl IntoExpr) -> Self { self.op("@>", rhs) }
}

// Core operators return `PsqlExpr`, so a dialect operator still applies after
// one: the chain never escapes into a type that has lost `@>`.
let e = PsqlExpr::from_expr(Expr::ident("tags")).contains("'{a}'").is_not_null();

Either way step is the only thing an added operator needs, and the parenthesisation rule is applied for it.

§Why the supertraits

A chain has to be able to hand its expression back — to nest it in another operator, or to store it in a clause. That is exactly what IntoExpr means, so Chain requires it instead of declaring a second method with the same job, and any chain value can therefore be passed to any slot in the library. IntoExprList is required for the same reason one step out: it is what lets a.and(b) take another chain value directly instead of the one-element tuple (b,). Both are one line for a dialect newtype, and requiring them here is how that requirement gets stated rather than discovered.

Required Methods§

Source

fn from_expr(e: Expr) -> Self

Wrap a finished expression back into the chain type.

The inverse of IntoExpr::into_expr; the two together are all a dialect has to supply.

Provided Methods§

Source

fn step(self, f: impl FnOnce(Expr) -> Expr) -> Self

One chain step: build a node from this expression, apply the parenthesisation rule, and return a chain again.

Every operator below is a one-line call to this, and so is every operator a dialect adds.

Source

fn op(self, op: &'static str, rhs: impl IntoExpr) -> Self

An arbitrary infix operator: self op rhs.

The generic escape hatch — a dialect-specific operator that needs nothing but a symbol is this call and no new code in core.

Source

fn eq(self, rhs: impl IntoExpr) -> Self

self = rhs.

Source

fn ne(self, rhs: impl IntoExpr) -> Self

self <> rhs. The standard spelling, which every dialect accepts.

Source

fn lt(self, rhs: impl IntoExpr) -> Self

self < rhs.

Source

fn lte(self, rhs: impl IntoExpr) -> Self

self <= rhs.

Source

fn gt(self, rhs: impl IntoExpr) -> Self

self > rhs.

Source

fn gte(self, rhs: impl IntoExpr) -> Self

self >= rhs.

Source

fn in_(self, vals: impl IntoExprList) -> Self

self IN (a, b, c).

The operands are always parenthesised, so a single sub-select operand comes out as IN (SELECT ..) and a row list as IN ((..), (..)).

Source

fn not_in(self, vals: impl IntoExprList) -> Self

self NOT IN (a, b, c).

Source

fn is_null(self) -> Self

self IS NULL.

Source

fn is_not_null(self) -> Self

self IS NOT NULL.

Source

fn is_distinct_from(self, rhs: impl IntoExpr) -> Self

self IS DISTINCT FROM rhs.

Source

fn is_not_distinct_from(self, rhs: impl IntoExpr) -> Self

self IS NOT DISTINCT FROM rhs.

Source

fn between(self, a: impl IntoExpr, b: impl IntoExpr) -> Self

self BETWEEN a AND b.

Source

fn not_between(self, a: impl IntoExpr, b: impl IntoExpr) -> Self

self NOT BETWEEN a AND b.

Source

fn like(self, rhs: impl IntoExpr) -> Self

self LIKE rhs.

Source

fn concat(self, others: impl IntoExprList) -> Self

self || a || b — string concatenation.

Source

fn and(self, others: impl IntoExprList) -> Self

self AND a AND b.

Source

fn or(self, others: impl IntoExprList) -> Self

self OR a OR b.

Source

fn plus(self, rhs: impl IntoExpr) -> Self

self + rhs.

Source

fn minus(self, rhs: impl IntoExpr) -> Self

self - rhs.

Source

fn as_(self, alias: impl IntoIdent) -> Expr

self AS "alias".

This one ends the chain — it returns an Expr, not Self. An alias is not an operand: nothing may be applied to x AS "y", and unlike every other method here the result is deliberately not parenthesised, because (x AS "y") is a syntax error in a select list.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§

Source§

impl Chain for Expr

An Expr is its own chain, so operators are available without any wrapper type. A dialect that wants its own type implements Chain for that instead.