Skip to main content

rupg_analyze/
rules.rs

1//! `QueryRewrite`, `RewriteQuery` and `fireRules` of `rewriteHandler.c`: the statements that the rules `ON INSERT`, `ON UPDATE` and `ON DELETE` of a table give for a write of the table.
2//!
3//! PostgreSQL rewrites the query trees. rupg rewrites the raw statements: an action of a rule gets the statement of the user, with no `RETURNING`, as the subquery `*rule*` first in its `FROM`, and the analysis reads that subquery as the rows that the statement writes. The names `old` and `new` of the action read the old row and the new row of each of these rows, as `rewriteRuleAction` puts the range table of the statement in the action and the values of the new row in place of `NEW`. The condition of the rule goes to the `WHERE` of the action, and the statement of the user gets the inverse of the condition of each rule `DO INSTEAD` with a condition, as `CopyAndAddInvertedQual` adds it.
4
5use rupg_catalog::Rule;
6use rupg_common::{Error, Result, SqlState};
7use rupg_sql::nodes::{
8    A_Star, Alias, BoolExpr, BoolExprType, CmdType, ColumnRef, Node, RangeSubselect, RangeVar,
9    ResTarget, RuleStmt, SelectStmt, SetOperation, WithClause,
10};
11use rupg_types::oid;
12
13use crate::agg::Kind;
14use crate::expr::{BoolOp, BoolTest, Expr, ExprKind, Var};
15use crate::from::Column;
16use crate::insert::Generated;
17use crate::walk::{self, Walker};
18use crate::{Analyzer, Env, Params, Query};
19
20/// The name of the subquery of an action that gives the rows of the statement of the user.
21const RULE: &str = "*rule*";
22
23/// A statement of the rewrite of a write by the rules of its table, in the order that `RewriteQuery` gives.
24#[derive(Clone, Debug)]
25pub struct Product {
26    /// The statement: the statement of the user, or an action of a rule with the statement of the user in its `FROM`.
27    pub stmt: Node,
28    /// `canSetTag`: true for the statement whose tag and number of rows are the result of the command.
29    pub sets_tag: bool,
30    /// True for an action of a rule, whose errors have no place in the statement of the user.
31    pub action: bool,
32    /// The conditions of the rules `DO INSTEAD` with `WHERE`. The statement writes only the rows for which no condition is true.
33    inverted: Vec<Node>,
34    /// The OID of the target table when the rewrite fired its rules for this statement.
35    fired: Option<u32>,
36    source: Source,
37}
38
39/// `querySource`: where a statement of the rewrite comes from.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41enum Source {
42    /// `QSRC_ORIGINAL`: the statement of the user.
43    Original,
44    /// `QSRC_INSTEAD_RULE`: an action of a rule `DO INSTEAD` with no condition.
45    Instead,
46    /// `QSRC_QUAL_INSTEAD_RULE`: an action of a rule `DO INSTEAD` with a condition.
47    QualInstead,
48    /// `QSRC_NON_INSTEAD_RULE`: an action of a rule `DO ALSO`.
49    Also,
50}
51
52/// A statement of the rewrite before the choice of the statement that sets the tag.
53struct Item {
54    stmt: Node,
55    source: Source,
56    inverted: Vec<Node>,
57    fired: Option<u32>,
58}
59
60/// The state of the analysis of the rules of a write.
61#[derive(Debug, Default)]
62pub(crate) struct State {
63    /// True while the analyzer reads the actions of `CREATE RULE`, whose target tables can have rules.
64    pub(crate) defining: bool,
65    /// The OID of the target table of the statement whose rules the rewrite fired.
66    pub(crate) fired: Option<u32>,
67    /// True when the next write gives the rows of the subquery `*rule*`: the new row of each row, then the old row, with no write.
68    pub(crate) rows: bool,
69    /// The columns of the target table of the last write that gave its rows.
70    pub(crate) columns: Vec<Column>,
71    /// The conditions that the next write inverts, which read `old` and `new`.
72    pub(crate) inverted: Vec<Node>,
73}
74
75/// The kind of a write and its target table.
76fn target_of(stmt: &Node) -> Option<(CmdType, &RangeVar)> {
77    match stmt {
78        Node::InsertStmt(s) => Some((CmdType::CMD_INSERT, s.relation.as_deref()?)),
79        Node::UpdateStmt(s) => Some((CmdType::CMD_UPDATE, s.relation.as_deref()?)),
80        Node::DeleteStmt(s) => Some((CmdType::CMD_DELETE, s.relation.as_deref()?)),
81        _ => None,
82    }
83}
84
85/// True for a write, which the subquery `*rule*` has.
86pub(crate) fn is_write(node: &Node) -> bool {
87    matches!(node, Node::InsertStmt(_) | Node::UpdateStmt(_) | Node::DeleteStmt(_))
88}
89
90/// The `ev_type` code of an event.
91fn event_code(event: CmdType) -> u8 {
92    match event {
93        CmdType::CMD_UPDATE => b'2',
94        CmdType::CMD_INSERT => b'3',
95        _ => b'4',
96    }
97}
98
99/// The word of an event in the errors.
100fn event_word(event: CmdType) -> &'static str {
101    match event {
102        CmdType::CMD_UPDATE => "UPDATE",
103        CmdType::CMD_INSERT => "INSERT",
104        _ => "DELETE",
105    }
106}
107
108/// True when the rule fires in the origin mode of `session_replication_role`, as `matchLocks` skips the rules that are disabled or that fire only on a replica.
109pub(crate) fn fires(rule: &Rule) -> bool {
110    rule.enabled != b'D' && rule.enabled != b'R'
111}
112
113/// An error for a rule that rupg does not take yet.
114fn not_yet(what: &str) -> Error {
115    Error::new(SqlState::FEATURE_NOT_SUPPORTED, format!("{what} is not supported yet"))
116}
117
118/// The walk that finds a name `old` or `new` of a rule.
119struct UsesRow(bool);
120
121impl Walker for UsesRow {
122    fn node(&mut self, node: &Node) -> bool {
123        if let Node::ColumnRef(c) = node
124            && c.fields.len() > 1
125            && let Some(Some(Node::String(first))) = c.fields.first()
126            && matches!(&**first, "old" | "new")
127        {
128            self.0 = true;
129            return true;
130        }
131        walk::walk(self, node)
132    }
133
134    fn select(&mut self, stmt: &SelectStmt) -> bool {
135        walk::select(self, stmt)
136    }
137}
138
139/// True when the node names `old` or `new`.
140fn uses_row(node: &Node) -> bool {
141    let mut w = UsesRow(false);
142    w.node(node);
143    w.0
144}
145
146/// True when a query of the `WITH` clause changes data.
147fn writes_in_with(with: Option<&WithClause>) -> bool {
148    with.is_some_and(|w| {
149        w.ctes.iter().flatten().any(|cte| match cte {
150            Node::CommonTableExpr(c) => {
151                c.ctequery.as_ref().is_some_and(|q| is_write(q) || matches!(q, Node::MergeStmt(_)))
152            }
153            _ => false,
154        })
155    })
156}
157
158/// The statement with no `RETURNING`, and its `WITH` clause, and true when it has `RETURNING`, `ON CONFLICT` or a condition of `WHERE`, and its parts of `FROM` or `USING`.
159struct Parts {
160    base: Node,
161    returning: bool,
162    on_conflict: bool,
163    filter: bool,
164    from: bool,
165    with: Option<WithClause>,
166}
167
168fn parts(stmt: &Node) -> Parts {
169    let mut base = stmt.clone();
170    let (returning, on_conflict, filter, from, with) = match &mut base {
171        Node::InsertStmt(s) => (
172            s.returningClause.take().is_some(),
173            s.onConflictClause.is_some(),
174            false,
175            false,
176            s.withClause.as_deref().cloned(),
177        ),
178        Node::UpdateStmt(s) => (
179            s.returningClause.take().is_some(),
180            false,
181            s.whereClause.is_some(),
182            !s.fromClause.is_empty(),
183            s.withClause.as_deref().cloned(),
184        ),
185        Node::DeleteStmt(s) => (
186            s.returningClause.take().is_some(),
187            false,
188            s.whereClause.is_some(),
189            !s.usingClause.is_empty(),
190            s.withClause.as_deref().cloned(),
191        ),
192        _ => (false, false, false, false, None),
193    };
194    Parts { base, returning, on_conflict, filter, from, with }
195}
196
197/// The `CREATE RULE` statement of the text that the catalog keeps for a rule.
198fn rule_stmt(rule: &Rule) -> Result<RuleStmt> {
199    let (list, _) = rupg_sql::parse(&rule.text).map_err(Error::from)?;
200    list.iter()
201        .flatten()
202        .find_map(|node| match node {
203            Node::RawStmt(raw) => match raw.stmt.as_ref() {
204                Some(Node::RuleStmt(stmt)) => Some((**stmt).clone()),
205                _ => None,
206            },
207            _ => None,
208        })
209        .ok_or_else(|| Error::internal("the text of a rule is not a rule"))
210}
211
212/// `make_and_qual`: the condition `node` and the condition `more`.
213fn and(node: Option<Node>, more: Option<&Node>) -> Option<Node> {
214    match (node, more) {
215        (Some(node), Some(more)) => Some(Node::BoolExpr(Box::new(BoolExpr {
216            boolop: BoolExprType::AND_EXPR,
217            args: vec![Some(node), Some(more.clone())],
218            location: -1,
219        }))),
220        (node, more) => node.or_else(|| more.cloned()),
221    }
222}
223
224/// `rewriteRuleAction`: the action with the statement of the user as `*rule*` first in its `FROM`, and the condition of the rule in its `WHERE`. An action that reads neither `old` nor `new`, with no condition that reads them, runs once for the statement, as the target table then leaves the join tree of the action.
225fn rule_action(parts: &Parts, action: &Node, qual: Option<&Node>, event: CmdType) -> Result<Node> {
226    let needs_rows = uses_row(action)
227        || qual.is_some_and(uses_row)
228        || event == CmdType::CMD_INSERT
229        || parts.filter;
230    if !needs_rows && parts.from {
231        return Err(not_yet(
232            "a rule action that does not read OLD or NEW for a statement with FROM or USING",
233        ));
234    }
235    let rows = needs_rows.then(|| {
236        Node::RangeSubselect(Box::new(RangeSubselect {
237            lateral: false,
238            subquery: Some(parts.base.clone()),
239            alias: Some(Box::new(Alias { aliasname: Some(RULE.into()), colnames: Vec::new() })),
240        }))
241    });
242    let mut action = action.clone();
243    match &mut action {
244        Node::InsertStmt(s) => {
245            if s.returningClause.is_some() {
246                return Err(not_yet("RETURNING in a rule"));
247            }
248            if rows.is_none() && qual.is_none() {
249                return Ok(action);
250            }
251            // DEFAULT VALUES becomes a SELECT with no columns, so that every
252            // column takes its default for each row that the qual keeps.
253            let select = s.selectStmt.get_or_insert_with(|| Node::SelectStmt(Box::default()));
254            let Node::SelectStmt(select) = select else {
255                return Err(not_yet("a rule action INSERT that is not a SELECT"));
256            };
257            if select.op != SetOperation::SETOP_NONE {
258                return Err(Error::new(
259                    SqlState::FEATURE_NOT_SUPPORTED,
260                    "conditional UNION/INTERSECT/EXCEPT statements are not implemented",
261                ));
262            }
263            if !select.valuesLists.is_empty() {
264                values_to_select(select)?;
265            }
266            if let Some(rows) = rows {
267                select.fromClause.insert(0, Some(rows));
268            }
269            select.whereClause = and(select.whereClause.take(), qual);
270        }
271        Node::UpdateStmt(s) => {
272            if s.returningClause.is_some() {
273                return Err(not_yet("RETURNING in a rule"));
274            }
275            if let Some(rows) = rows {
276                s.fromClause.insert(0, Some(rows));
277            }
278            s.whereClause = and(s.whereClause.take(), qual);
279        }
280        Node::DeleteStmt(s) => {
281            if s.returningClause.is_some() {
282                return Err(not_yet("RETURNING in a rule"));
283            }
284            if let Some(rows) = rows {
285                s.usingClause.insert(0, Some(rows));
286            }
287            s.whereClause = and(s.whereClause.take(), qual);
288        }
289        Node::NotifyStmt(_) => return Err(not_yet("NOTIFY in a rule")),
290        _ => return Err(not_yet("SELECT in a rule")),
291    }
292    Ok(action)
293}
294
295/// The `VALUES` of an `INSERT` as a `SELECT` that can have `FROM` and `WHERE`: one row gives the list of `SELECT`, and more rows are a `LATERAL` subquery.
296fn values_to_select(select: &mut SelectStmt) -> Result<()> {
297    let has_default = select.valuesLists.iter().flatten().any(|row| match row {
298        Node::List(items) => items.iter().flatten().any(|n| matches!(n, Node::SetToDefault(_))),
299        _ => false,
300    });
301    if has_default {
302        return Err(not_yet("DEFAULT in VALUES of a rule action"));
303    }
304    if let [Some(Node::List(items))] = select.valuesLists.as_slice() {
305        select.targetList = items
306            .iter()
307            .map(|item| {
308                Some(Node::ResTarget(Box::new(ResTarget {
309                    name: None,
310                    indirection: Vec::new(),
311                    val: item.clone(),
312                    location: -1,
313                })))
314            })
315            .collect();
316        select.valuesLists.clear();
317        return Ok(());
318    }
319    let values = SelectStmt {
320        valuesLists: std::mem::take(&mut select.valuesLists),
321        ..SelectStmt::default()
322    };
323    let name = "*VALUES*";
324    select.fromClause.push(Some(Node::RangeSubselect(Box::new(RangeSubselect {
325        lateral: true,
326        subquery: Some(Node::SelectStmt(Box::new(values))),
327        alias: Some(Box::new(Alias { aliasname: Some(name.into()), colnames: Vec::new() })),
328    }))));
329    let star = ColumnRef {
330        fields: vec![Some(Node::String(name.into())), Some(Node::A_Star(Box::new(A_Star {})))],
331        location: -1,
332    };
333    select.targetList = vec![Some(Node::ResTarget(Box::new(ResTarget {
334        name: None,
335        indirection: Vec::new(),
336        val: Some(Node::ColumnRef(Box::new(star))),
337        location: -1,
338    })))];
339    Ok(())
340}
341
342/// `QueryRewrite` for a write: the statements of the rewrite by the rules of the target table, in order, or `None` when no rule fires.
343///
344/// # Errors
345///
346/// The errors of `RewriteQuery`: a rule that fires itself again, `RETURNING` with a rule `DO INSTEAD`, and `ON CONFLICT` for a table with rules. `0A000` for an action that rupg does not take yet.
347pub fn query_rewrite(stmt: &Node, env: &dyn Env) -> Result<Option<Vec<Product>>> {
348    let Some(catalog) = env.catalog() else { return Ok(None) };
349    if !catalog.rules().any(|r| r.event != b'1') || target_of(stmt).is_none() {
350        return Ok(None);
351    }
352    let mut events = Vec::new();
353    let Some(items) = rewrite_query(stmt, Source::Original, env, &mut events)? else {
354        return Ok(None);
355    };
356    // The statement of the user sets the tag, or else the last action of a rule `DO INSTEAD` of the same kind of write.
357    let kind = target_of(stmt).map(|(kind, _)| kind);
358    let tag = items.iter().position(|i| i.source == Source::Original).or_else(|| {
359        items.iter().rposition(|i| {
360            matches!(i.source, Source::Instead | Source::QualInstead)
361                && target_of(&i.stmt).map(|(k, _)| k) == kind
362        })
363    });
364    let products = items
365        .into_iter()
366        .enumerate()
367        .map(|(n, item)| Product {
368            stmt: item.stmt,
369            sets_tag: Some(n) == tag,
370            action: item.source != Source::Original,
371            inverted: item.inverted,
372            fired: item.fired,
373            source: item.source,
374        })
375        .collect();
376    Ok(Some(products))
377}
378
379/// The checks of `BeginCopyTo` on the rewrite of the query of `COPY` by the rules of its table: the error for a rewrite that is not one statement.
380pub fn copy_rules_error(products: &[Product]) -> Option<Error> {
381    let text = match products {
382        [] => "DO INSTEAD NOTHING rules are not supported for COPY",
383        [_] => return None,
384        _ => products
385            .iter()
386            .find_map(|p| match p.source {
387                Source::QualInstead => {
388                    Some("conditional DO INSTEAD rules are not supported for COPY")
389                }
390                Source::Also => Some("DO ALSO rules are not supported for COPY"),
391                _ => None,
392            })
393            .unwrap_or("multi-statement DO INSTEAD rules are not supported for COPY"),
394    };
395    Some(Error::new(SqlState::FEATURE_NOT_SUPPORTED, text))
396}
397
398/// `RewriteQuery` for a write: `None` when no rule of the target table fires for it.
399fn rewrite_query(
400    stmt: &Node,
401    source: Source,
402    env: &dyn Env,
403    events: &mut Vec<(u32, CmdType)>,
404) -> Result<Option<Vec<Item>>> {
405    let Some(catalog) = env.catalog() else { return Ok(None) };
406    let Some((event, rv)) = target_of(stmt) else { return Ok(None) };
407    let Some(oid) = Analyzer::new(env, &Params::default()).lookup_relation(rv)? else {
408        return Ok(None);
409    };
410    let name = catalog.relation(oid).map_or("", |r| r.name.as_str());
411    let code = event_code(event);
412    let mut rules: Vec<&Rule> =
413        catalog.rules().filter(|r| r.relation == oid && r.event == code && fires(r)).collect();
414    rules.sort_by(|a, b| a.name.cmp(&b.name));
415    let parts = parts(stmt);
416    let has_update = catalog.rules().any(|r| r.relation == oid && r.event == b'2');
417    if parts.on_conflict && (!rules.is_empty() || has_update) {
418        return Err(Error::new(
419            SqlState::FEATURE_NOT_SUPPORTED,
420            "INSERT with ON CONFLICT clause cannot be used with table that has INSERT or UPDATE rules",
421        ));
422    }
423    if rules.is_empty() {
424        return Ok(None);
425    }
426    if writes_in_with(parts.with.as_ref()) {
427        return Err(not_yet("a statement in WITH that changes data for a table with rules"));
428    }
429    let mut instead = false;
430    let mut inverted: Option<Vec<Node>> = None;
431    let mut products = Vec::new();
432    for rule in rules {
433        let rule_stmt = rule_stmt(rule)?;
434        let qual = rule_stmt.whereClause.as_ref();
435        let action_source = match (rule.instead, qual) {
436            (true, Some(_)) => Source::QualInstead,
437            (true, None) => {
438                instead = true;
439                Source::Instead
440            }
441            (false, _) => Source::Also,
442        };
443        // `CopyAndAddInvertedQual`: the statement of the user writes only the rows for which the condition of no rule `DO INSTEAD` is true.
444        if let (Source::QualInstead, Some(qual)) = (action_source, qual)
445            && !instead
446        {
447            inverted.get_or_insert_default().push(qual.clone());
448        }
449        for action in rule_stmt.actions.iter().flatten() {
450            products.push((rule_action(&parts, action, qual, event)?, action_source));
451        }
452    }
453    let mut result = Vec::new();
454    if !products.is_empty() {
455        if events.contains(&(oid, event)) {
456            return Err(Error::new(
457                SqlState::INVALID_OBJECT_DEFINITION,
458                format!("infinite recursion detected in rules for relation \"{name}\""),
459            ));
460        }
461        events.push((oid, event));
462        for (product, source) in products {
463            match rewrite_query(&product, source, env, events)? {
464                Some(items) => result.extend(items),
465                None => {
466                    result.push(Item { stmt: product, source, inverted: Vec::new(), fired: None })
467                }
468            }
469        }
470        events.pop();
471    }
472    if (instead || inverted.is_some()) && parts.returning {
473        let word = event_word(event);
474        return Err(Error::new(
475            SqlState::FEATURE_NOT_SUPPORTED,
476            format!("cannot perform {word} RETURNING on relation \"{name}\""),
477        )
478        .with_hint(format!(
479            "You need an unconditional ON {word} DO INSTEAD rule with a RETURNING clause."
480        )));
481    }
482    // The statement of the user comes first for an `INSERT`, and last for an `UPDATE` or a `DELETE`.
483    if !instead {
484        let item = Item {
485            stmt: stmt.clone(),
486            source,
487            inverted: inverted.unwrap_or_default(),
488            fired: Some(oid),
489        };
490        if event == CmdType::CMD_INSERT {
491            result.insert(0, item);
492        } else {
493            result.push(item);
494        }
495    }
496    Ok(Some(result))
497}
498
499/// [`crate::analyze`] for a statement of [`query_rewrite`].
500///
501/// # Errors
502///
503/// The errors of the analysis. An error of an action of a rule has no place.
504pub fn analyze_product(product: &Product, env: &dyn Env, params: &Params) -> Result<Query> {
505    let mut analyzer = Analyzer::new(env, params);
506    analyzer.rule.fired = product.fired;
507    analyzer.rule.inverted.clone_from(&product.inverted);
508    let query = crate::analyze_in(analyzer, &product.stmt, None);
509    if product.action { query.map_err(|error| error.with_position(None)) } else { query }
510}
511
512/// The expression with the value of each column of `row` in place of the column of the relation 0, as `ReplaceVarsFromTargetList` puts the new row in a generation expression.
513pub(crate) fn replace_row(expr: &mut Expr, row: &[Expr]) {
514    expr.each_mut(0, &mut |e, depth| {
515        let ExprKind::Var(var) = e.kind else { return false };
516        if var.levels_up != depth || var.relation != 0 {
517            return false;
518        }
519        let index = usize::try_from(var.attnum).ok().and_then(|n| n.checked_sub(1));
520        let Some(value) = index.and_then(|i| row.get(i)) else { return false };
521        let mut value = value.clone();
522        value.raise(depth);
523        *e = value;
524        true
525    });
526}
527
528/// The names and the values of the columns of a row of the target table, with no dropped column.
529pub(crate) fn named_row(columns: &[Column], row: Vec<Expr>) -> Vec<(String, Expr)> {
530    columns
531        .iter()
532        .zip(row)
533        .filter(|(c, _)| !c.is_dropped())
534        .map(|(c, e)| (c.name.clone(), e))
535        .collect()
536}
537
538/// The columns of the relation 0 as the values of a row.
539pub(crate) fn relation_row(columns: &[Column], relation: usize, offset: usize) -> Vec<Expr> {
540    columns
541        .iter()
542        .enumerate()
543        .map(|(i, c)| {
544            let attnum = i16::try_from(offset + i + 1).unwrap_or(i16::MAX);
545            let mut expr = Expr::new(ExprKind::Var(Var { relation, attnum, levels_up: 0 }), c.ty);
546            expr.typmod = c.typmod;
547            expr
548        })
549        .collect()
550}
551
552/// The new row with the value of each generated column of `generated`, from the values of the other columns. The rules see the value of a virtual generated column too, as `get_generated_columns` gives it.
553pub(crate) fn with_generated(mut row: Vec<Expr>, generated: &[Generated]) -> Vec<Expr> {
554    let values = row.clone();
555    for (index, expr, _) in generated {
556        let mut expr = expr.clone();
557        replace_row(&mut expr, &values);
558        if let Some(slot) = row.get_mut(*index) {
559            *slot = expr;
560        }
561    }
562    row
563}
564
565/// `AddQual`: the condition and the conditions of `more`.
566pub(crate) fn and_filter(filter: Option<Expr>, more: Vec<Expr>) -> Option<Expr> {
567    let mut parts: Vec<Expr> = filter.into_iter().chain(more).collect();
568    match parts.len() {
569        0 => None,
570        1 => parts.pop(),
571        _ => Some(Expr::new(ExprKind::Bool(BoolOp::And, parts), oid::BOOL)),
572    }
573}
574
575impl Analyzer<'_> {
576    /// `CopyAndAddInvertedQual`: the conditions of the rules `DO INSTEAD` that a write inverts, each as `IS NOT TRUE`, with `old` as the old row and `new` as the new row of the write.
577    pub(crate) fn inverted_quals(
578        &mut self,
579        quals: &[Node],
580        old: Option<Vec<(String, Expr)>>,
581        new: Option<Vec<(String, Expr)>>,
582    ) -> Result<Vec<Expr>> {
583        let outer = std::mem::take(&mut self.scope);
584        let result = self.inverted_in_scope(quals, old, new);
585        self.scope = outer;
586        result
587    }
588
589    fn inverted_in_scope(
590        &mut self,
591        quals: &[Node],
592        old: Option<Vec<(String, Expr)>>,
593        new: Option<Vec<(String, Expr)>>,
594    ) -> Result<Vec<Expr>> {
595        for (name, row) in [("old", old), ("new", new)] {
596            if let Some(row) = row {
597                let item = self.rule_item(name, row);
598                self.push_item(item);
599            }
600        }
601        let mut out = Vec::with_capacity(quals.len());
602        for qual in quals {
603            let expr = self.with_kind(Kind::Where, |a| a.transform(Some(qual)))?;
604            let expr = self.coerce_to_boolean(expr, "WHERE")?;
605            out.push(Expr::new(
606                ExprKind::BooleanTest(Box::new(expr), BoolTest::IsNotTrue),
607                oid::BOOL,
608            ));
609        }
610        Ok(out)
611    }
612}