pub trait Chain:
Sized
+ IntoExpr
+ IntoExprList {
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, "e("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§
Sourcefn from_expr(e: Expr) -> Self
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§
Sourcefn step(self, f: impl FnOnce(Expr) -> Expr) -> Self
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.
Sourcefn op(self, op: &'static str, rhs: impl IntoExpr) -> Self
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.
Sourcefn ne(self, rhs: impl IntoExpr) -> Self
fn ne(self, rhs: impl IntoExpr) -> Self
self <> rhs. The standard spelling, which every dialect accepts.
Sourcefn in_(self, vals: impl IntoExprList) -> Self
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 ((..), (..)).
Sourcefn not_in(self, vals: impl IntoExprList) -> Self
fn not_in(self, vals: impl IntoExprList) -> Self
self NOT IN (a, b, c).
Sourcefn is_not_null(self) -> Self
fn is_not_null(self) -> Self
self IS NOT NULL.
Sourcefn is_distinct_from(self, rhs: impl IntoExpr) -> Self
fn is_distinct_from(self, rhs: impl IntoExpr) -> Self
self IS DISTINCT FROM rhs.
Sourcefn is_not_distinct_from(self, rhs: impl IntoExpr) -> Self
fn is_not_distinct_from(self, rhs: impl IntoExpr) -> Self
self IS NOT DISTINCT FROM rhs.
Sourcefn not_between(self, a: impl IntoExpr, b: impl IntoExpr) -> Self
fn not_between(self, a: impl IntoExpr, b: impl IntoExpr) -> Self
self NOT BETWEEN a AND b.
Sourcefn concat(self, others: impl IntoExprList) -> Self
fn concat(self, others: impl IntoExprList) -> Self
self || a || b — string concatenation.
Sourcefn and(self, others: impl IntoExprList) -> Self
fn and(self, others: impl IntoExprList) -> Self
self AND a AND b.
Sourcefn or(self, others: impl IntoExprList) -> Self
fn or(self, others: impl IntoExprList) -> Self
self OR a OR b.
Sourcefn as_(self, alias: impl IntoIdent) -> Expr
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".