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::{EnumValue, Value};
12
13use super::{enum_endpoint, enum_label, enum_range, EnumLabelCatalog};
14use crate::ast::EnumFunctionOperation;
15use crate::error::{Result, SQLError};
16use crate::expr::hashing::hash_bytes_uint32_extended;
17
18fn enum_argument(value: &Value, type_oid: u32) -> Result<Option<&EnumValue>> {
19    match value {
20        Value::Null => Ok(None),
21        Value::Enum(label) if label.type_oid() == type_oid => Ok(Some(label)),
22        other => Err(SQLError::Internal(format!(
23            "enum support function bound to type OID {type_oid} received {other:?}"
24        ))),
25    }
26}
27
28fn ordering_value(ordering: Ordering) -> Value {
29    Value::Int(match ordering {
30        Ordering::Less => -1,
31        Ordering::Equal => 0,
32        Ordering::Greater => 1,
33    })
34}
35
36/// Evaluate one bound enum support function. Strict operations return NULL for any NULL argument before reading the catalog.
37pub fn enum_function_value(
38    catalog: Option<&dyn EnumLabelCatalog>,
39    operation: EnumFunctionOperation,
40    type_oid: u32,
41    arguments: &[Value],
42) -> Result<Value> {
43    let arity = match operation {
44        EnumFunctionOperation::First
45        | EnumFunctionOperation::Last
46        | EnumFunctionOperation::Range
47        | EnumFunctionOperation::Hash => 1,
48        _ => 2,
49    };
50    if arguments.len() != arity {
51        return Err(SQLError::Internal(format!(
52            "{} received {} arguments",
53            operation.label(),
54            arguments.len()
55        )));
56    }
57    if operation.is_strict() && arguments.iter().any(|value| matches!(value, Value::Null)) {
58        return Ok(Value::Null);
59    }
60    let first = enum_argument(&arguments[0], type_oid)?;
61    let lost_argument =
62        || SQLError::Internal(format!("{} lost a strict argument", operation.label()));
63    match operation {
64        EnumFunctionOperation::First => enum_endpoint(catalog, type_oid, false),
65        EnumFunctionOperation::Last => enum_endpoint(catalog, type_oid, true),
66        EnumFunctionOperation::Range => enum_range(catalog, type_oid, None, None),
67        EnumFunctionOperation::BoundedRange => enum_range(
68            catalog,
69            type_oid,
70            first,
71            enum_argument(&arguments[1], type_oid)?,
72        ),
73        // `hashenum` and `hashenumextended` hash the label OID with `hash_uint32` and `hash_uint32_extended`.
74        EnumFunctionOperation::Hash => {
75            let oid = enum_label(catalog, first.ok_or_else(lost_argument)?)?.oid;
76            let hash = hash_bytes_uint32_extended(oid, 0) as u32;
77            Ok(Value::Int(i64::from(hash as i32)))
78        }
79        EnumFunctionOperation::ExtendedHash => {
80            let Value::Int(seed) = arguments[1] else {
81                return Err(SQLError::Internal(format!(
82                    "hashenumextended received seed {:?}",
83                    arguments[1]
84                )));
85            };
86            let oid = enum_label(catalog, first.ok_or_else(lost_argument)?)?.oid;
87            Ok(Value::Int(
88                hash_bytes_uint32_extended(oid, seed as u64) as i64
89            ))
90        }
91        _ => {
92            let (Some(left), Some(right)) = (first, enum_argument(&arguments[1], type_oid)?) else {
93                return Err(lost_argument());
94            };
95            comparison_value(operation, left, right)
96        }
97    }
98}
99
100/// The comparison support functions, which order two values of the bound type by label key.
101fn comparison_value(
102    operation: EnumFunctionOperation,
103    left: &EnumValue,
104    right: &EnumValue,
105) -> Result<Value> {
106    let ordering = left.key().cmp(right.key());
107    Ok(match operation {
108        EnumFunctionOperation::Compare => ordering_value(ordering),
109        EnumFunctionOperation::Equal => Value::Bool(ordering.is_eq()),
110        EnumFunctionOperation::NotEqual => Value::Bool(ordering.is_ne()),
111        EnumFunctionOperation::Less => Value::Bool(ordering.is_lt()),
112        EnumFunctionOperation::Greater => Value::Bool(ordering.is_gt()),
113        EnumFunctionOperation::LessEqual => Value::Bool(ordering.is_le()),
114        EnumFunctionOperation::GreaterEqual => Value::Bool(ordering.is_ge()),
115        EnumFunctionOperation::Smaller => {
116            Value::Enum(if ordering.is_le() { left } else { right }.clone())
117        }
118        EnumFunctionOperation::Larger => {
119            Value::Enum(if ordering.is_ge() { left } else { right }.clone())
120        }
121        other => {
122            return Err(SQLError::Internal(format!(
123                "{} is not an enum comparison",
124                other.label()
125            )))
126        }
127    })
128}