pub enum Expr {
Show 16 variants
Raw(Cow<'static, str>),
Literal(Cow<'static, str>),
Ident(Vec<Cow<'static, str>>),
Arg(Value),
Args(Vec<Value>),
NamedArg(Cow<'static, str>),
Template {
sql: Cow<'static, str>,
args: Vec<RawArg>,
},
Group(Vec<Expr>),
Binary {
lhs: Box<Expr>,
op: &'static str,
rhs: Box<Expr>,
},
Prefix {
op: &'static str,
operand: Box<Expr>,
},
Postfix {
operand: Box<Expr>,
op: &'static str,
},
Join {
exprs: Vec<Expr>,
sep: &'static str,
},
Func {
name: Cow<'static, str>,
args: Vec<Expr>,
over: Option<Box<Expr>>,
},
Case {
whens: Vec<(Expr, Expr)>,
else_: Option<Box<Expr>>,
},
Cast {
expr: Box<Expr>,
type_name: Cow<'static, str>,
},
Custom(Arc<dyn Expression>),
}Expand description
A SQL expression, as data.
Where bob threads bob.Expression — a Go interface — through every slot and
gives each shape its own unexported struct, keelson has one algebraic type.
The reasons, in the order they matter:
- Expressions stay inspectable. Layer 4 rewrites a parsed query into
clause-reconstruction code, and a rewriter needs to look at what it has.
A
Box<dyn Expression>can only be rendered. - No dynamic dispatch on the hot path, and
Cloneis a memcpy plus a couple of refcount bumps. - One
matchrenders everything, so the spacing and separator decisions that bob spreads over a dozen files sit in one screen where they can be compared against the grammar.
The escape hatch is Expr::Custom, which holds an erased
Expression. Dialect-specific shapes — PostgreSQL’s ROWS FROM, MySQL’s
index hints — live in their own crate as an ordinary Expression and travel
through core as Custom, so keelson-core never learns about them.
This enum is deliberately not #[non_exhaustive]: exhaustive matching is
the point, and a downstream rewriter that stops compiling when a variant is
added is being told something true.
§Strings
Identifiers, raw SQL and type names are Cow<'static, str>: a literal borrows
and costs nothing, a computed name is owned, and no lifetime parameter escapes
into any public type. Operators and separators are &'static str — they are
always literals, in core and in a dialect crate alike.
Variants§
Raw(Cow<'static, str>)
SQL written out verbatim. ? is not rewritten — use
Expr::Template for that.
This is bob’s “progressive enhancement” in the enum: a hand-written
fragment is a first-class expression, and keyword fragments like AND or
IS NULL are nothing more than this.
Literal(Cow<'static, str>)
A single-quoted SQL string literal — bob’s S(). Renders 'abc'.
Nothing is escaped, exactly as in bob. This is for keywords, enum labels
and other SQL the program itself wrote; user input belongs in
Expr::Arg, where it is bound rather than interpolated.
Ident(Vec<Cow<'static, str>>)
A dot-joined quoted identifier: ["users", "id"] renders "users"."id".
Empty parts are skipped, so an unset qualifier needs no branch at the call site, and an entirely empty list renders nothing at all.
Arg(Value)
One bound argument, rendered as the dialect’s placeholder.
Args(Vec<Value>)
Several bound arguments, comma-separated: $1, $2, $3.
Not parenthesised — it is usually written into a slot that brings its own
parentheses, such as VALUES (..). Wrap it in Expr::Group when the
parentheses are wanted; that is what super::arg_group does. An empty
list renders NULL, matching bob.
NamedArg(Cow<'static, str>)
A named argument placeholder, for preparing a statement whose values arrive at bind time.
Binds nothing and consumes no positional slot. On a dialect with no named
arguments this records Error::NoNamedArgs
on the writer, which build then surfaces.
Template
Raw SQL whose ? placeholders are rewritten to the dialect’s own syntax,
with args interleaved. See RawArg and super::template.
Fields
Group(Vec<Expr>)
A parenthesised, comma-separated list: (a, b, c).
One element is how a plain parenthesised expression is written. Empty
renders (NULL), matching bob — a row constructor with no columns is
still a value.
Binary
An infix operator: lhs op rhs, one space either side.
Fields
Prefix
A prefix operator: op operand. NOT x, -x.
Postfix
A postfix operator: operand op. x IS NULL, x DESC.
Join
A separator-joined sequence — bob’s Join. Renders nothing when empty.
This is the general-purpose “several fragments in a row” node: AND/OR
chains, BETWEEN a AND b, a clause built out of keyword fragments.
Fields
sep: &'static strWritten between consecutive parts, verbatim. Use " " for bob’s
default; see Expr::join.
Func
A function call, optionally with an OVER window: avg(x) OVER (w).
Core keeps only what every dialect has. DISTINCT, FILTER (WHERE ..)
and WITHIN GROUP are per-dialect and belong to a dialect’s own function
builder, reaching core through Expr::Custom.
Fields
Case
CASE WHEN c THEN t .. [ELSE e] END.
At least one WHEN is required; with none this records
Error::Incomplete and writes nothing, which
is bob’s error turned into the recorded-failure form.
Fields
Cast
CAST(expr AS type_name).
Fields
Custom(Arc<dyn Expression>)
A dialect-specific expression core knows nothing about.
The one place dynamic dispatch survives, and the reason core never needs a
variant for ROWS FROM, MATCH .. AGAINST or anything else that belongs
to exactly one grammar.
Implementations§
Source§impl Expr
impl Expr
Sourcepub fn template(
sql: impl Into<Cow<'static, str>>,
args: impl IntoIterator<Item = RawArg>,
) -> Expr
pub fn template( sql: impl Into<Cow<'static, str>>, args: impl IntoIterator<Item = RawArg>, ) -> Expr
Raw SQL with ? placeholders and their replacements.
Sourcepub fn literal(s: impl Into<Cow<'static, str>>) -> Expr
pub fn literal(s: impl Into<Cow<'static, str>>) -> Expr
A single-quoted string literal — bob’s S().
Sourcepub fn ident(parts: impl IntoIdent) -> Expr
pub fn ident(parts: impl IntoIdent) -> Expr
A quoted identifier. ident("age") and ident(("users", "id")) both
work; see IntoIdent.
Empty parts are dropped here rather than at render time, so the stored node is exactly what will be written.
Sourcepub fn args<V>(vals: impl IntoIterator<Item = V>) -> Exprwhere
V: ToValue,
pub fn args<V>(vals: impl IntoIterator<Item = V>) -> Exprwhere
V: ToValue,
A comma-separated list of bound arguments.
Sourcepub fn placeholders(n: usize) -> Expr
pub fn placeholders(n: usize) -> Expr
n unbound placeholders — bob’s Placeholder(n).
Each one binds NULL, so the shape of the statement is right and the
values are supplied by whatever rebinds it.
Sourcepub fn group(items: impl IntoExprList) -> Expr
pub fn group(items: impl IntoExprList) -> Expr
A parenthesised list. A single expression gives plain parentheses.
Sourcepub fn binary(lhs: impl IntoExpr, op: &'static str, rhs: impl IntoExpr) -> Expr
pub fn binary(lhs: impl IntoExpr, op: &'static str, rhs: impl IntoExpr) -> Expr
An infix operator applied to two operands.
Sourcepub fn join(items: impl IntoExprList) -> Expr
pub fn join(items: impl IntoExprList) -> Expr
Space-separated parts — bob’s Join with its default separator.
Sourcepub fn join_with(sep: &'static str, items: impl IntoExprList) -> Expr
pub fn join_with(sep: &'static str, items: impl IntoExprList) -> Expr
Parts joined by sep, written verbatim.
Unlike bob, an empty separator means an empty separator. bob silently
substitutes a space for Sep: "", which is a trap in a language where the
zero value is what you get by leaving a field out; here the separator is
always passed explicitly, and Expr::join is the space-separated form.
Sourcepub fn func(name: impl Into<Cow<'static, str>>, args: impl IntoExprList) -> Expr
pub fn func(name: impl Into<Cow<'static, str>>, args: impl IntoExprList) -> Expr
A function call with no window.
Sourcepub fn cast(
expr: impl IntoExpr,
type_name: impl Into<Cow<'static, str>>,
) -> Expr
pub fn cast( expr: impl IntoExpr, type_name: impl Into<Cow<'static, str>>, ) -> Expr
CAST(expr AS type_name).
Sourcepub fn custom(e: impl Expression + 'static) -> Expr
pub fn custom(e: impl Expression + 'static) -> Expr
Wrap an arbitrary Expression so it can travel through core.
Sourcepub fn is_atomic(&self) -> bool
pub fn is_atomic(&self) -> bool
Whether this expression renders as a self-delimiting fragment, so that parentheses around it would add nothing.
§The parenthesisation rule
This predicate and grouped are bob’s expr.X — the
single most output-visible decision in the whole library. Every operator
in bob’s chain wraps its result in parentheses unless the result is one
of a small set of shapes, and that is precisely why bob emits
("id" = $1) for an equality but "users"."id" for a column.
Atomic, and so never wrapped:
RawandTemplate— the author wrote the SQL and gets it back unedited.Literal—'abc'is one token.Ident—"users"."id"is one token.Arg,Args,NamedArg— a placeholder list is normally written into a slot that supplies its own parentheses, such asVALUES (..).Group— already parenthesised.
Everything else is wrapped, including Custom: core
cannot see inside it, and bob’s fallback for an unrecognised expression is
to wrap.
bob has a further arm — an expression that is already a built chain
value is returned unchanged — which has no counterpart here and needs
none. Every chain step applies this rule to its own result, so a chain
value is always Group or atomic by construction, and re-applying the
rule is a no-op. That invariant is what makes NOT ("a" = $1) come out
with one set of parentheses rather than two.
Trait Implementations§
Source§impl Chain for Expr
impl Chain for Expr
Source§fn step(self, f: impl FnOnce(Expr) -> Expr) -> Self
fn step(self, f: impl FnOnce(Expr) -> Expr) -> Self
Source§fn op(self, op: &'static str, rhs: impl IntoExpr) -> Self
fn op(self, op: &'static str, rhs: impl IntoExpr) -> Self
self op rhs. Read moreSource§fn ne(self, rhs: impl IntoExpr) -> Self
fn ne(self, rhs: impl IntoExpr) -> Self
self <> rhs. The standard spelling, which every dialect accepts.Source§fn in_(self, vals: impl IntoExprList) -> Self
fn in_(self, vals: impl IntoExprList) -> Self
self IN (a, b, c). Read moreSource§fn not_in(self, vals: impl IntoExprList) -> Self
fn not_in(self, vals: impl IntoExprList) -> Self
self NOT IN (a, b, c).Source§fn is_not_null(self) -> Self
fn is_not_null(self) -> Self
self IS NOT NULL.Source§fn is_distinct_from(self, rhs: impl IntoExpr) -> Self
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
fn is_not_distinct_from(self, rhs: impl IntoExpr) -> Self
self IS NOT DISTINCT FROM rhs.Source§fn 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.Source§fn concat(self, others: impl IntoExprList) -> Self
fn concat(self, others: impl IntoExprList) -> Self
self || a || b — string concatenation.Source§fn and(self, others: impl IntoExprList) -> Self
fn and(self, others: impl IntoExprList) -> Self
self AND a AND b.Source§fn or(self, others: impl IntoExprList) -> Self
fn or(self, others: impl IntoExprList) -> Self
self OR a OR b.Source§impl Expression for Expr
impl Expression for Expr
Source§impl IntoExprList for Expr
impl IntoExprList for Expr
Source§fn into_expr_list(self) -> Vec<Expr>
fn into_expr_list(self) -> Vec<Expr>
Auto Trait Implementations§
impl !RefUnwindSafe for Expr
impl !UnwindSafe for Expr
impl Freeze for Expr
impl Send for Expr
impl Sync for Expr
impl Unpin for Expr
impl UnsafeUnpin for Expr
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> SqliteOps for Twhere
T: Chain,
impl<T> SqliteOps for Twhere
T: Chain,
Source§fn glob(self, rhs: impl IntoExpr) -> Self
fn glob(self, rhs: impl IntoExpr) -> Self
self GLOB rhs — Unix file-glob matching, and case-sensitive, which is
what distinguishes it from LIKE.Source§fn not_regexp(self, rhs: impl IntoExpr) -> Self
fn not_regexp(self, rhs: impl IntoExpr) -> Self
self NOT REGEXP rhs. See regexp about availability.Source§fn match_(self, rhs: impl IntoExpr) -> Self
fn match_(self, rhs: impl IntoExpr) -> Self
self MATCH rhs — the full-text and R-tree extension operator. Read moreSource§fn like_escape(self, pattern: impl IntoExpr, escape: impl IntoExpr) -> Self
fn like_escape(self, pattern: impl IntoExpr, escape: impl IntoExpr) -> Self
self LIKE pattern ESCAPE escape. Read moreSource§fn not_like_escape(self, pattern: impl IntoExpr, escape: impl IntoExpr) -> Self
fn not_like_escape(self, pattern: impl IntoExpr, escape: impl IntoExpr) -> Self
self NOT LIKE pattern ESCAPE escape.Source§fn json_get(self, rhs: impl IntoExpr) -> Self
fn json_get(self, rhs: impl IntoExpr) -> Self
self -> rhs — the field or element, as JSON text. SQLite 3.38 and later. Read moreSource§fn json_get_text(self, rhs: impl IntoExpr) -> Self
fn json_get_text(self, rhs: impl IntoExpr) -> Self
self ->> rhs — the field or element as a SQL text, integer or real.