Skip to main content

uqa_sql/expr/enums/
functions.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! The `anyenum` support functions bound to one concrete enum type.
8
9use 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
30/// Evaluate one bound enum support function. Strict operations return NULL for any NULL argument before reading the catalog.
31pub 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
40/// Evaluate with the calling expression's retained comparison state. Equality,
41/// hashes and type-only operations do not initialize or inspect that state.
42pub 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
64/// Preserve the caller's catalog and function state while admitting selected values, label keys, range elements and array metadata before allocation.
65pub 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        // `hashenum` and `hashenumextended` hash the label OID with `hash_uint32` and `hash_uint32_extended`.
111        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
149/// Equality reads OIDs directly; ordering consults label order only on the slow path.
150fn 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;