1use std::cmp::Ordering;
10
11use uqa_core::Value;
12
13use super::{enum_label_oid, type_name, EnumLabelCatalog};
14use crate::ast::BinaryOp;
15use crate::error::{Result, SQLError};
16
17mod state;
18#[cfg(test)]
19mod tests;
20pub use state::{EnumComparisonState, EnumTypeComparisonStates};
21
22pub(in crate::expr) fn comparison_order(
23 left: &Value,
24 right: &Value,
25 catalog: Option<&dyn EnumLabelCatalog>,
26 state: Option<&EnumComparisonState>,
27 container: bool,
28) -> Result<Option<Ordering>> {
29 let Some(catalog) = catalog else {
30 return Ok(None);
31 };
32 let (Some(left_oid), Some(right_oid)) = (
33 comparison_identity(catalog, left)?,
34 comparison_identity(catalog, right)?,
35 ) else {
36 return Ok(None);
37 };
38 let shared = if container {
39 let type_oid = match left {
40 Value::Enum(label) => label.type_oid(),
41 Value::Datum(datum) => datum.type_oid(),
42 _ => unreachable!("enum identity has a declared type"),
43 };
44 catalog
45 .enum_type_comparison_states()
46 .map(|states| states.get(type_oid))
47 } else {
48 None
49 };
50 compare_oids(
51 Some(catalog),
52 left_oid,
53 right_oid,
54 shared.as_deref().or(state),
55 )
56 .map(Some)
57}
58
59pub fn comparison_identity(catalog: &dyn EnumLabelCatalog, value: &Value) -> Result<Option<u32>> {
62 match value {
63 Value::Enum(_) => oid(Some(catalog), value),
64 Value::Datum(datum) if catalog.enum_type_labels(datum.type_oid())?.is_some() => {
65 super::super::datums::enum_label_oid(datum).map(Some)
66 }
67 _ => Ok(None),
68 }
69}
70
71pub fn eval_comparison(
74 op: BinaryOp,
75 left: &Value,
76 right: &Value,
77 catalog: Option<&dyn EnumLabelCatalog>,
78 state: Option<&EnumComparisonState>,
79) -> Result<Option<Value>> {
80 if matches!(
81 op,
82 BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | BinaryOp::Divide
83 ) {
84 return Ok(None);
85 }
86 if matches!(left, Value::Null) || matches!(right, Value::Null) {
87 return Ok(Some(Value::Null));
88 }
89 let Some(catalog) = catalog else {
90 return Ok(None);
91 };
92 let (Some(left), Some(right)) = (
93 comparison_identity(catalog, left)?,
94 comparison_identity(catalog, right)?,
95 ) else {
96 return Ok(None);
97 };
98 let value = match op {
99 BinaryOp::Equal => left == right,
100 BinaryOp::NotEqual => left != right,
101 _ => {
102 let order = compare_oids(Some(catalog), left, right, state)?;
103 match op {
104 BinaryOp::Less => order.is_lt(),
105 BinaryOp::LessEqual => order.is_le(),
106 BinaryOp::Greater => order.is_gt(),
107 BinaryOp::GreaterEqual => order.is_ge(),
108 _ => unreachable!("arithmetic has no enum comparison"),
109 }
110 }
111 };
112 Ok(Some(Value::Bool(value)))
113}
114
115pub(super) fn oid(catalog: Option<&dyn EnumLabelCatalog>, value: &Value) -> Result<Option<u32>> {
116 match value {
117 Value::Null => Ok(None),
118 Value::Enum(label) => label
119 .label_oid()
120 .map(Ok)
121 .unwrap_or_else(|| enum_label_oid(catalog, label))
122 .map(Some),
123 Value::Datum(datum) => super::super::datums::enum_label_oid(datum).map(Some),
124 other => Err(SQLError::Internal(format!(
125 "enum support function received {other:?}"
126 ))),
127 }
128}
129
130pub(super) fn equal(
131 catalog: Option<&dyn EnumLabelCatalog>,
132 left: &Value,
133 right: &Value,
134) -> Result<bool> {
135 if let Some(order) = native_order(left, right) {
136 return Ok(order.is_eq());
137 }
138 Ok(oid(catalog, left)? == oid(catalog, right)?)
139}
140
141fn native_order(left: &Value, right: &Value) -> Option<Ordering> {
142 match (left, right) {
143 (Value::Enum(left), Value::Enum(right)) if left.type_oid() == right.type_oid() => {
144 Some(left.key().cmp(right.key()))
145 }
146 _ => None,
147 }
148}
149
150pub(super) fn compare(
151 catalog: Option<&dyn EnumLabelCatalog>,
152 left: &Value,
153 right: &Value,
154 state: Option<&EnumComparisonState>,
155) -> Result<Ordering> {
156 let legacy_keys = catalog.is_none()
157 && matches!((left, right), (Value::Enum(left), Value::Enum(right)) if left.label_oid().is_none() && right.label_oid().is_none());
158 if state.is_none() || legacy_keys {
159 if let Some(order) = native_order(left, right) {
160 return Ok(order);
161 }
162 }
163 let (Some(left), Some(right)) = (oid(catalog, left)?, oid(catalog, right)?) else {
164 return Err(SQLError::Internal(
165 "enum comparison lost a strict argument".into(),
166 ));
167 };
168 compare_oids(catalog, left, right, state)
169}
170
171fn compare_oids(
172 catalog: Option<&dyn EnumLabelCatalog>,
173 left: u32,
174 right: u32,
175 state: Option<&EnumComparisonState>,
176) -> Result<Ordering> {
177 if left == right || (left & 1 == 0 && right & 1 == 0) {
180 return Ok(left.cmp(&right));
181 }
182 let left_entry = catalog
183 .map(|catalog| catalog.enum_label_position(left))
184 .transpose()?
185 .flatten();
186 let type_oid = if let Some(oid) = state.and_then(EnumComparisonState::cached_type) {
187 oid
188 } else {
189 let (oid, _) = left_entry.ok_or_else(|| SQLError::Routine {
190 sqlstate: "22P03".into(),
191 message: format!("invalid internal value for enum: {left}"),
192 })?;
193 state.map_or(oid, |state| state.remember_type(oid))
194 };
195 let Some((_, left_position)) = left_entry.filter(|(oid, _)| *oid == type_oid) else {
196 return Err(missing_cached_label(catalog, type_oid, left)?);
197 };
198 let Some((_, right_position)) = catalog
199 .map(|catalog| catalog.enum_label_position(right))
200 .transpose()?
201 .flatten()
202 .filter(|(right_type, _)| *right_type == type_oid)
203 else {
204 return Err(missing_cached_label(catalog, type_oid, right)?);
205 };
206 Ok(left_position.cmp(&right_position))
207}
208
209fn missing_cached_label(
210 catalog: Option<&dyn EnumLabelCatalog>,
211 type_oid: u32,
212 label_oid: u32,
213) -> Result<SQLError> {
214 Ok(SQLError::Routine {
215 sqlstate: "XX000".into(),
216 message: format!(
217 "enum value {label_oid} not found in cache for enum {}",
218 type_name(catalog, type_oid)?
219 ),
220 })
221}