Skip to main content

toasty_core/stmt/
expr_in_subquery.rs

1use crate::stmt::{Node, Visit, VisitMut};
2
3use super::{Expr, Query};
4
5/// Tests whether a value is in the results of a subquery.
6///
7/// Returns `true` if `expr` matches any row returned by `query`, or the
8/// opposite when `negated` is set. The flag makes `NOT IN` a predicate in
9/// its own right rather than the negation of `IN`: statement normalization
10/// attaches variant guards to the predicate before expanding the negated
11/// form to `not(in_subquery(..))`, so the guards end up outside the
12/// negation.
13///
14/// # Examples
15///
16/// ```text
17/// in_subquery(x, select(...))      // returns `true` if `x` is in the subquery results
18/// not_in_subquery(x, select(...))  // returns `true` if `x` is not in the subquery results
19/// ```
20#[derive(Debug, Clone, PartialEq)]
21pub struct ExprInSubquery {
22    /// The value to search for.
23    pub expr: Box<Expr>,
24
25    /// The subquery to search within.
26    pub query: Box<Query>,
27
28    /// Whether the check is `NOT IN`.
29    pub negated: bool,
30
31    /// Whether this compares relation keys rather than application values.
32    ///
33    /// Relation lifting sets this when it rewrites a relation membership into
34    /// a key comparison. A relation key identifies a row only when it is
35    /// present, so an absent key on either side never matches. A value
36    /// membership compares the values themselves.
37    ///
38    /// Temporary: this marks the one membership that needs two-valued results
39    /// before application predicates have their own representation. Remove it
40    /// once <https://github.com/tokio-rs/toasty/pull/1251> lands, since that
41    /// lowers lifted relation memberships as application predicates.
42    pub relation_key: bool,
43}
44
45impl Expr {
46    /// Creates an `IN (subquery)` expression: `lhs IN (SELECT ...)`.
47    pub fn in_subquery(lhs: impl Into<Self>, rhs: impl Into<Query>) -> Self {
48        ExprInSubquery {
49            expr: Box::new(lhs.into()),
50            query: Box::new(rhs.into()),
51            negated: false,
52            relation_key: false,
53        }
54        .into()
55    }
56
57    /// Creates a `NOT IN (subquery)` expression: `lhs NOT IN (SELECT ...)`.
58    pub fn not_in_subquery(lhs: impl Into<Self>, rhs: impl Into<Query>) -> Self {
59        ExprInSubquery {
60            expr: Box::new(lhs.into()),
61            query: Box::new(rhs.into()),
62            negated: true,
63            relation_key: false,
64        }
65        .into()
66    }
67
68    /// Returns `true` if this expression is an `IN (subquery)` check.
69    pub fn is_in_subquery(&self) -> bool {
70        matches!(self, Self::InSubquery(_))
71    }
72}
73
74impl Node for ExprInSubquery {
75    fn visit<V: Visit>(&self, mut visit: V) {
76        visit.visit_expr_in_subquery(self);
77    }
78
79    fn visit_mut<V: VisitMut>(&mut self, mut visit: V) {
80        visit.visit_expr_in_subquery_mut(self);
81    }
82}
83
84impl From<ExprInSubquery> for Expr {
85    fn from(value: ExprInSubquery) -> Self {
86        Self::InSubquery(value)
87    }
88}