1use std::cmp::Ordering;
10
11use uqa_core::memory::{Produced, ProductionControl};
12use uqa_core::Value;
13
14use super::{
15 enum_endpoint_with_control, physical, range_by_oid_with_control, EnumComparisonState,
16 EnumLabelCatalog,
17};
18use crate::ast::EnumFunctionOperation;
19use crate::error::{Result, SQLError};
20use crate::expr::hashing::hash_bytes_uint32_extended;
21
22fn ordering_value(ordering: Ordering) -> Value {
23 Value::Int(match ordering {
24 Ordering::Less => -1,
25 Ordering::Equal => 0,
26 Ordering::Greater => 1,
27 })
28}
29
30pub fn enum_function_value(
32 catalog: Option<&dyn EnumLabelCatalog>,
33 operation: EnumFunctionOperation,
34 type_oid: u32,
35 arguments: &[Value],
36) -> Result<Value> {
37 enum_function_value_with_state(catalog, operation, type_oid, arguments, None)
38}
39
40pub fn enum_function_value_with_state(
43 catalog: Option<&dyn EnumLabelCatalog>,
44 operation: EnumFunctionOperation,
45 type_oid: u32,
46 arguments: &[Value],
47 state: Option<&EnumComparisonState>,
48) -> Result<Value> {
49 enum_function_value_with_control(
50 catalog,
51 operation,
52 type_oid,
53 arguments,
54 state,
55 &ProductionControl::uncontrolled(),
56 )
57 .map(|value| {
58 value
59 .into_uncontrolled()
60 .expect("ordinary enum support result")
61 })
62}
63
64pub fn enum_function_value_with_control(
66 catalog: Option<&dyn EnumLabelCatalog>,
67 operation: EnumFunctionOperation,
68 type_oid: u32,
69 arguments: &[Value],
70 state: Option<&EnumComparisonState>,
71 control: &ProductionControl<'_>,
72) -> Result<Produced<Value>> {
73 control.check()?;
74 let arity = match operation {
75 EnumFunctionOperation::First
76 | EnumFunctionOperation::Last
77 | EnumFunctionOperation::Range
78 | EnumFunctionOperation::Hash => 1,
79 _ => 2,
80 };
81 if arguments.len() != arity {
82 return Err(SQLError::Internal(format!(
83 "{} received {} arguments",
84 operation.label(),
85 arguments.len()
86 )));
87 }
88 if operation.is_strict() && arguments.iter().any(|value| matches!(value, Value::Null)) {
89 return plain(Value::Null, control);
90 }
91 let value = match operation {
92 EnumFunctionOperation::First => {
93 return enum_endpoint_with_control(catalog, type_oid, false, control)
94 }
95 EnumFunctionOperation::Last => {
96 return enum_endpoint_with_control(catalog, type_oid, true, control)
97 }
98 EnumFunctionOperation::Range => {
99 return range_by_oid_with_control(catalog, type_oid, None, None, control)
100 }
101 EnumFunctionOperation::BoundedRange => {
102 return range_by_oid_with_control(
103 catalog,
104 type_oid,
105 physical::oid(catalog, &arguments[0])?,
106 physical::oid(catalog, &arguments[1])?,
107 control,
108 )
109 }
110 EnumFunctionOperation::Hash => {
112 let oid = strict_oid(catalog, &arguments[0])?;
113 let hash = hash_bytes_uint32_extended(oid, 0) as u32;
114 Value::Int(i64::from(hash as i32))
115 }
116 EnumFunctionOperation::ExtendedHash => {
117 let Value::Int(seed) = arguments[1] else {
118 return Err(SQLError::Internal(format!(
119 "hashenumextended received seed {:?}",
120 arguments[1]
121 )));
122 };
123 let oid = strict_oid(catalog, &arguments[0])?;
124 Value::Int(hash_bytes_uint32_extended(oid, seed as u64) as i64)
125 }
126 _ => {
127 return comparison_value(
128 catalog,
129 operation,
130 &arguments[0],
131 &arguments[1],
132 state,
133 control,
134 )
135 }
136 };
137 plain(value, control)
138}
139
140fn plain(value: Value, control: &ProductionControl<'_>) -> Result<Produced<Value>> {
141 Ok(control.finish(value, control.empty_reservation())?)
142}
143
144fn strict_oid(catalog: Option<&dyn EnumLabelCatalog>, value: &Value) -> Result<u32> {
145 physical::oid(catalog, value)?
146 .ok_or_else(|| SQLError::Internal("enum support function lost a strict argument".into()))
147}
148
149fn comparison_value(
151 catalog: Option<&dyn EnumLabelCatalog>,
152 operation: EnumFunctionOperation,
153 left: &Value,
154 right: &Value,
155 state: Option<&EnumComparisonState>,
156 control: &ProductionControl<'_>,
157) -> Result<Produced<Value>> {
158 if operation == EnumFunctionOperation::Equal {
159 return plain(Value::Bool(physical::equal(catalog, left, right)?), control);
160 }
161 if operation == EnumFunctionOperation::NotEqual {
162 return plain(
163 Value::Bool(!physical::equal(catalog, left, right)?),
164 control,
165 );
166 }
167 let ordering = physical::compare(catalog, left, right, state)?;
168 let value = match operation {
169 EnumFunctionOperation::Compare => ordering_value(ordering),
170 EnumFunctionOperation::Less => Value::Bool(ordering.is_lt()),
171 EnumFunctionOperation::Greater => Value::Bool(ordering.is_gt()),
172 EnumFunctionOperation::LessEqual => Value::Bool(ordering.is_le()),
173 EnumFunctionOperation::GreaterEqual => Value::Bool(ordering.is_ge()),
174 EnumFunctionOperation::Smaller => {
175 return Ok(control.copy_value(if ordering.is_lt() { left } else { right })?)
176 }
177 EnumFunctionOperation::Larger => {
178 return Ok(control.copy_value(if ordering.is_gt() { left } else { right })?)
179 }
180 other => {
181 return Err(SQLError::Internal(format!(
182 "{} is not an enum comparison",
183 other.label()
184 )))
185 }
186 };
187 plain(value, control)
188}
189
190#[cfg(test)]
191mod tests;