Skip to main content

qbrs_core/
render.rs

1//! Rendering `ExprKind` into a SQL string plus a positional parameter list.
2//! Generic over `D: Dialect` for identifier quoting and placeholder style,
3//! but `ExprKind`/`Value` themselves stay closed, non-generic types, so this
4//! costs one instantiation per dialect used in a program rather than one per
5//! query shape.
6
7use crate::dialect::Dialect;
8use crate::expr::{BinOp, CastTarget, ExprKind, SortDir, Value};
9
10/// Where rendered SQL goes. A bind parameter is *told* to the sink rather
11/// than written as text, which is what lets the same renderer produce either
12/// a finished statement or a `Fragment` whose parameters aren't numbered
13/// yet — with no character standing in for one, and so nothing to escape.
14pub(crate) trait Sink {
15    fn text(&mut self, s: &str);
16    fn ch(&mut self, c: char);
17    fn bind(&mut self, value: &Value);
18}
19
20/// Builds a finished statement, numbering each parameter as it arrives.
21#[doc(hidden)]
22pub struct QuerySink<D> {
23    sql: String,
24    params: Vec<Value>,
25    _dialect: std::marker::PhantomData<fn() -> D>,
26}
27
28impl<D: Dialect> QuerySink<D> {
29    pub(crate) fn new() -> Self {
30        QuerySink {
31            sql: String::new(),
32            params: Vec::new(),
33            _dialect: std::marker::PhantomData,
34        }
35    }
36
37    pub(crate) fn finish(self) -> (String, Vec<Value>) {
38        (self.sql, self.params)
39    }
40}
41
42impl<D: Dialect> Sink for QuerySink<D> {
43    fn text(&mut self, s: &str) {
44        self.sql.push_str(s);
45    }
46    fn ch(&mut self, c: char) {
47        self.sql.push(c);
48    }
49    fn bind(&mut self, value: &Value) {
50        self.params.push(value.clone());
51        D::write_placeholder(self.params.len(), &mut self.sql);
52    }
53}
54
55/// Builds a `Fragment`: a parameter starts a new segment instead of being
56/// written, so its eventual number is decided by whoever splices it.
57pub(crate) struct FragmentSink(Fragment);
58
59impl FragmentSink {
60    pub(crate) fn new() -> Self {
61        FragmentSink(Fragment::empty())
62    }
63
64    pub(crate) fn finish(self) -> Fragment {
65        self.0
66    }
67}
68
69impl Sink for FragmentSink {
70    fn text(&mut self, s: &str) {
71        self.0.tail().push_str(s);
72    }
73    fn ch(&mut self, c: char) {
74        self.0.tail().push(c);
75    }
76    fn bind(&mut self, value: &Value) {
77        self.0.rest.push((value.clone(), String::new()));
78    }
79}
80
81pub(crate) fn render_expr<D: Dialect>(expr: &ExprKind, sink: &mut dyn Sink) {
82    match expr {
83        ExprKind::Column { table, name } => {
84            render_ident::<D>(sink, table);
85            sink.ch('.');
86            render_ident::<D>(sink, name);
87        }
88        ExprKind::Value(v) => sink.bind(v),
89        ExprKind::BinOp { op, lhs, rhs } => {
90            sink.ch('(');
91            render_expr::<D>(lhs, sink);
92            sink.text(match op {
93                BinOp::Eq => " = ",
94                BinOp::Ne => " <> ",
95                BinOp::Lt => " < ",
96                BinOp::Lte => " <= ",
97                BinOp::Gt => " > ",
98                BinOp::Gte => " >= ",
99                BinOp::Like => " LIKE ",
100            });
101            render_expr::<D>(rhs, sink);
102            sink.ch(')');
103        }
104        ExprKind::And(lhs, rhs) => render_bool_pair::<D>(lhs, "AND", rhs, sink),
105        ExprKind::Or(lhs, rhs) => render_bool_pair::<D>(lhs, "OR", rhs, sink),
106        ExprKind::Not(inner) => {
107            sink.text("(NOT ");
108            render_expr::<D>(inner, sink);
109            sink.ch(')');
110        }
111        ExprKind::Cast { expr, target } => {
112            sink.text("CAST(");
113            render_expr::<D>(expr, sink);
114            sink.text(" AS ");
115            sink.text(match target {
116                CastTarget::BigInt => D::CAST_BIGINT,
117                CastTarget::Double => D::CAST_DOUBLE,
118            });
119            sink.ch(')');
120        }
121        ExprKind::Func { name, arg } => {
122            sink.text(name);
123            sink.ch('(');
124            match arg {
125                Some(arg) => render_expr::<D>(arg, sink),
126                None => sink.ch('*'),
127            }
128            sink.ch(')');
129        }
130        ExprKind::IsNull { expr, negated } => {
131            sink.ch('(');
132            render_expr::<D>(expr, sink);
133            sink.text(if *negated {
134                " IS NOT NULL)"
135            } else {
136                " IS NULL)"
137            });
138        }
139        ExprKind::Always(yes) => sink.text(if *yes { "TRUE" } else { "FALSE" }),
140        ExprKind::InList { expr, values } => {
141            sink.ch('(');
142            render_expr::<D>(expr, sink);
143            sink.text(" IN (");
144            for (i, v) in values.iter().enumerate() {
145                if i > 0 {
146                    sink.text(", ");
147                }
148                render_expr::<D>(v, sink);
149            }
150            sink.text("))");
151        }
152        ExprKind::Exists {
153            body,
154            selection,
155            negated,
156        } => {
157            sink.text(if *negated {
158                "(NOT EXISTS ("
159            } else {
160                "(EXISTS ("
161            });
162            body.render_into::<D>(selection, sink);
163            sink.text("))");
164        }
165        ExprKind::Template { head, rest } => {
166            // Same defensive parentheses as an embedded fragment: authored
167            // text has no precedence the renderer knows about.
168            sink.ch('(');
169            sink.text(head);
170            for (arg, text) in rest {
171                render_expr::<D>(arg, sink);
172                sink.text(text);
173            }
174            sink.ch(')');
175        }
176        ExprKind::Window {
177            func,
178            partition_by,
179            order_by,
180        } => {
181            // No defensive parens here, unlike `Template`: `OVER` only attaches
182            // to a bare function-call syntax node, so `(row_number()) OVER
183            // (..)` would not be valid SQL.
184            sink.text(func);
185            sink.text(" OVER (");
186            render_expr_list::<D>(sink, "PARTITION BY ", partition_by);
187            let keyword = if partition_by.is_empty() {
188                "ORDER BY "
189            } else {
190                " ORDER BY "
191            };
192            render_order_by::<D>(sink, keyword, order_by);
193            sink.ch(')');
194        }
195    }
196}
197
198#[doc(hidden)]
199#[derive(Debug, Clone)]
200/// One item in a rendered `SELECT`/`RETURNING` list. `label` is `Some` only
201/// for an item given a `expr::LabelKey` label, which is the only thing that
202/// emits `AS`.
203pub struct SelectItem {
204    pub(crate) kind: ExprKind,
205    pub(crate) label: Option<&'static str>,
206}
207
208impl SelectItem {
209    pub(crate) fn bare(kind: ExprKind) -> Self {
210        SelectItem { kind, label: None }
211    }
212
213    pub(crate) fn labeled(kind: ExprKind, label: &'static str) -> Self {
214        SelectItem {
215            kind,
216            label: Some(label),
217        }
218    }
219}
220
221/// Renders a comma-separated `SELECT`/`RETURNING` list, emitting each item's
222/// `AS` label where it has one.
223pub(crate) fn render_select_list<D: Dialect>(items: &[SelectItem], sink: &mut dyn Sink) {
224    for (i, item) in items.iter().enumerate() {
225        if i > 0 {
226            sink.text(", ");
227        }
228        render_expr::<D>(&item.kind, sink);
229        if let Some(label) = item.label {
230            sink.text(" AS ");
231            render_ident::<D>(sink, label);
232        }
233    }
234}
235
236/// A piece of SQL destined to be embedded in a larger query: a subquery, a
237/// CTE body, a set-operation branch. Held as the
238/// text *between* its bind parameters — `head`, then one `(param, text)`
239/// pair per parameter — so a parameter is a position rather than a
240/// character: nothing has to be escaped, re-splicing an already-spliced
241/// fragment can't confuse the two, and there is no way to hold a parameter
242/// with no text on either side of it.
243#[derive(Debug, Clone)]
244pub(crate) struct Fragment {
245    head: String,
246    rest: Vec<(Value, String)>,
247}
248
249impl Fragment {
250    fn empty() -> Self {
251        Fragment {
252            head: String::new(),
253            rest: Vec::new(),
254        }
255    }
256
257    /// Where the next text goes: after the last parameter, or in `head`
258    /// while there are none.
259    fn tail(&mut self) -> &mut String {
260        match self.rest.last_mut() {
261            Some((_, text)) => text,
262            None => &mut self.head,
263        }
264    }
265
266    /// Appends this fragment to whatever is being rendered, handing each of
267    /// its parameters to the sink in turn.
268    pub(crate) fn splice_into(&self, sink: &mut dyn Sink) {
269        sink.text(&self.head);
270        for (value, text) in &self.rest {
271            sink.bind(value);
272            sink.text(text);
273        }
274    }
275}
276
277/// Renders an identifier with the dialect's quoting.
278/// A dotted name is qualified, not one identifier: `analytics.events` is a
279/// table in a schema, and quoting it whole asks the database for a relation
280/// with a dot in its name. Only `#[table(name = "..")]` can contain one —
281/// every other name here comes from a Rust identifier.
282pub(crate) fn render_ident<D: Dialect>(sink: &mut dyn Sink, ident: &str) {
283    for (i, part) in ident.split('.').enumerate() {
284        if i > 0 {
285            sink.ch('.');
286        }
287        render_ident_part::<D>(sink, part);
288    }
289}
290
291fn render_ident_part<D: Dialect>(sink: &mut dyn Sink, ident: &str) {
292    sink.ch(D::IDENTIFIER_QUOTE);
293    for c in ident.chars() {
294        // A quote inside an identifier is escaped by doubling it, in every
295        // dialect this crate speaks. `#[table(name = "..")]` takes an
296        // arbitrary string, so an unescaped one would end the identifier.
297        if c == D::IDENTIFIER_QUOTE {
298            sink.ch(c);
299        }
300        sink.ch(c);
301    }
302    sink.ch(D::IDENTIFIER_QUOTE);
303}
304
305fn render_bool_pair<D: Dialect>(lhs: &ExprKind, joiner: &str, rhs: &ExprKind, sink: &mut dyn Sink) {
306    sink.ch('(');
307    render_expr::<D>(lhs, sink);
308    sink.ch(' ');
309    sink.text(joiner);
310    sink.ch(' ');
311    render_expr::<D>(rhs, sink);
312    sink.ch(')');
313}
314
315/// How a sort direction is spelled. Shared by a statement's `ORDER BY`, a
316/// window's, and a set operation's — which orders by ordinal position and so
317/// can't go through `render_order_by`.
318pub(crate) fn dir_keyword(dir: SortDir) -> &'static str {
319    match dir {
320        SortDir::Asc => " ASC",
321        SortDir::Desc => " DESC",
322    }
323}
324
325/// `SELECT count(*) FROM (<query>) AS qbrs_total` — how this crate counts a
326/// query whose rows aren't one per matching row. Written once, since the
327/// alias is part of the shape.
328pub(crate) fn render_count_wrapped<D: Dialect>(
329    sink: &mut QuerySink<D>,
330    body: impl FnOnce(&mut QuerySink<D>),
331) {
332    sink.text("SELECT count(*) FROM (");
333    body(sink);
334    sink.text(") AS ");
335    render_ident::<D>(sink, "qbrs_total");
336}
337
338/// A comma-separated expression list behind a keyword — `GROUP BY`,
339/// `PARTITION BY` — or nothing at all when there are none.
340pub(crate) fn render_expr_list<D: Dialect>(sink: &mut dyn Sink, keyword: &str, list: &[ExprKind]) {
341    if list.is_empty() {
342        return;
343    }
344    sink.text(keyword);
345    for (i, e) in list.iter().enumerate() {
346        if i > 0 {
347            sink.text(", ");
348        }
349        render_expr::<D>(e, sink);
350    }
351}
352
353/// The same, with each key's sort direction — a statement's `ORDER BY` and a
354/// window's `OVER (.. ORDER BY ..)` are one clause written in two places.
355pub(crate) fn render_order_by<D: Dialect>(
356    sink: &mut dyn Sink,
357    keyword: &str,
358    keys: &[(ExprKind, SortDir)],
359) {
360    if keys.is_empty() {
361        return;
362    }
363    sink.text(keyword);
364    for (i, (e, dir)) in keys.iter().enumerate() {
365        if i > 0 {
366            sink.text(", ");
367        }
368        render_expr::<D>(e, sink);
369        sink.text(dir_keyword(*dir));
370    }
371}
372
373/// `WHERE`/`HAVING`: a keyword, then the conditions AND-folded, or nothing
374/// at all when there are none. Shared by every statement that has such a
375/// clause, so all four spell it the same way.
376pub(crate) fn render_and_list<D: Dialect>(sink: &mut dyn Sink, keyword: &str, list: &[ExprKind]) {
377    if list.is_empty() {
378        return;
379    }
380    sink.text(keyword);
381    for (i, e) in list.iter().enumerate() {
382        if i > 0 {
383            sink.text(" AND ");
384        }
385        render_expr::<D>(e, sink);
386    }
387}