uqa_sql/semantics/
membership.rs1use crate::{ast::FunctionDispatch, ScalarExpr};
10use uqa_core::Value;
11
12pub struct MembershipOperands<'a> {
14 pub value: &'a ScalarExpr,
15 pub items: MembershipItems<'a>,
16 pub negated: bool,
17}
18
19#[derive(Clone, Copy)]
21pub enum MembershipItems<'a> {
22 Expressions(&'a [ScalarExpr]),
23 Constants(&'a [Value]),
24}
25
26pub enum MembershipItem<'a> {
27 Expression(&'a ScalarExpr),
28 Constant(&'a Value),
29}
30
31impl<'a> MembershipItems<'a> {
32 #[must_use]
33 pub fn len(self) -> usize {
34 match self {
35 Self::Expressions(items) => items.len(),
36 Self::Constants(items) => items.len(),
37 }
38 }
39
40 #[must_use]
41 pub fn is_empty(self) -> bool {
42 self.len() == 0
43 }
44
45 pub fn iter(self) -> impl Iterator<Item = MembershipItem<'a>> {
46 let (expressions, constants): (&[ScalarExpr], &[Value]) = match self {
47 Self::Expressions(items) => (items, &[]),
48 Self::Constants(items) => (&[], items),
49 };
50 expressions
51 .iter()
52 .map(MembershipItem::Expression)
53 .chain(constants.iter().map(MembershipItem::Constant))
54 }
55}
56
57#[must_use]
59pub fn membership_operands(expression: &ScalarExpr) -> Option<MembershipOperands<'_>> {
60 match expression {
61 ScalarExpr::InList {
62 expr,
63 list,
64 negated,
65 } => Some(MembershipOperands {
66 value: expr,
67 items: MembershipItems::Expressions(list),
68 negated: *negated,
69 }),
70 ScalarExpr::Func {
71 binding: Some(binding),
72 args,
73 ..
74 } => {
75 let [value, array, ScalarExpr::Literal(Value::Str(operator))] = args.as_slice() else {
76 return None;
77 };
78 let items = match array {
79 ScalarExpr::Array(items) => MembershipItems::Expressions(items),
80 ScalarExpr::Literal(Value::Array(array))
81 | ScalarExpr::TypedLiteral {
82 value: Value::Array(array),
83 ..
84 } if array.dimensions().len() <= 1 => MembershipItems::Constants(array.elements()),
85 _ => return None,
86 };
87 let negated = match (binding.dispatch, operator.as_str()) {
88 (Some(FunctionDispatch::AnyOperator), "=") => false,
89 (Some(FunctionDispatch::AllOperator), "<>") if !items.is_empty() => true,
90 _ => return None,
91 };
92 Some(MembershipOperands {
93 value,
94 items,
95 negated,
96 })
97 }
98 _ => None,
99 }
100}
101
102pub(crate) fn references_current_row(
104 expression: &ScalarExpr,
105 schema: Option<&crate::RowSchema>,
106) -> bool {
107 let mut found = false;
108 expression.visit(&mut |node| {
109 found |= match node {
110 ScalarExpr::Column(column) => {
111 schema.is_none_or(|schema| schema.resolves_local_column(None, column))
112 }
113 ScalarExpr::QualifiedColumn { qualifier, column } => {
114 schema.is_none_or(|schema| schema.resolves_local_column(Some(qualifier), column))
115 }
116 ScalarExpr::Position(position) => schema.is_none_or(|schema| {
117 schema
118 .slot(*position)
119 .is_some_and(|slot| schema.slot_is_local(slot))
120 }),
121 ScalarExpr::InternalColumn(column) => schema.is_none_or(|schema| {
122 schema
123 .internal_slot(*column)
124 .is_some_and(|slot| schema.slot_is_local(slot))
125 }),
126 ScalarExpr::QualifiedStar(qualifier) => schema.is_none_or(|schema| {
127 schema
128 .identities()
129 .iter()
130 .any(|identity| identity.qualifier() == Some(qualifier.as_str()))
131 }),
132 _ => false,
133 };
134 });
135 found
136}