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}