Skip to main content

uqa_sql/expr/binary/
comparison.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! SQL NULL and row comparison rules share the native controlled value owners.
8
9use super::super::enums::{EnumComparisonState, EnumLabelCatalog};
10use super::{BinaryOp, Result, SQLError, Value};
11use std::cmp::Ordering;
12use uqa_core::memory::ProductionControl;
13
14mod records;
15
16#[cfg(test)]
17pub(in crate::expr) fn eval_comparison_op(op: BinaryOp, l: &Value, r: &Value) -> Result<Value> {
18    Ok(eval_comparison_truth(op, l, r)?
19        .map(Value::Bool)
20        .unwrap_or(Value::Null))
21}
22
23/// Compare two values without allocating an intermediate [`Value::Bool`]. `None` represents SQL UNKNOWN, including an undecided anonymous row comparison.
24#[inline]
25pub fn eval_comparison_truth(op: BinaryOp, l: &Value, r: &Value) -> Result<Option<bool>> {
26    eval_comparison_truth_with_control(op, l, r, &ProductionControl::uncontrolled())
27}
28
29/// Evaluate the same three-valued comparison while admitting the native numeric, JSONB and array comparison workspace to the caller's allowance.
30pub fn eval_comparison_truth_with_control(
31    op: BinaryOp,
32    l: &Value,
33    r: &Value,
34    control: &ProductionControl<'_>,
35) -> Result<Option<bool>> {
36    control.check()?;
37    let out = match op {
38        BinaryOp::Equal => values_equal_nullable_with_control(l, r, control)?,
39        BinaryOp::NotEqual => values_equal_nullable_with_control(l, r, control)?.map(|v| !v),
40        BinaryOp::Less => compare_nullable_with_control(l, r, control)?.map(|v| v.is_lt()),
41        BinaryOp::LessEqual => compare_nullable_with_control(l, r, control)?.map(|v| v.is_le()),
42        BinaryOp::Greater => compare_nullable_with_control(l, r, control)?.map(|v| v.is_gt()),
43        BinaryOp::GreaterEqual => compare_nullable_with_control(l, r, control)?.map(|v| v.is_ge()),
44        _ => {
45            return Err(SQLError::Internal(format!(
46                "non-comparison operator {op:?} reached comparison evaluation"
47            )))
48        }
49    };
50    Ok(out)
51}
52
53/// Borrow physical type metadata from scalar hooks even when the embedder provides no enum capability.
54pub fn eval_comparison_truth_with_engine(
55    op: BinaryOp,
56    left: &Value,
57    right: &Value,
58    control: &ProductionControl<'_>,
59    engine: Option<&dyn crate::expr::EngineHook>,
60    state: Option<&EnumComparisonState>,
61) -> Result<Option<bool>> {
62    eval_comparison_truth_with_deferred_state(op, left, right, control, engine, || state)
63}
64
65/// Primitive scalar comparisons need no catalog or private enum call state. Resolve that state only when the evaluated carriers require the general comparison path; operand evaluation and its SQL ordering remain the caller's responsibility.
66#[inline]
67pub fn eval_comparison_truth_with_deferred_state<'a>(
68    op: BinaryOp,
69    left: &Value,
70    right: &Value,
71    control: &ProductionControl<'_>,
72    engine: Option<&dyn crate::expr::EngineHook>,
73    state: impl FnOnce() -> Option<&'a EnumComparisonState>,
74) -> Result<Option<bool>> {
75    control.check()?;
76    let primitive = match (left, right) {
77        (Value::Null, _) | (_, Value::Null) => Some(None),
78        (Value::Int(_), Value::Int(_))
79        | (Value::Float(_), Value::Float(_))
80        | (Value::Bool(_), Value::Bool(_)) => Some(Some(left.cmp(right))),
81        _ => None,
82    };
83    if let Some(order) = primitive {
84        return match op {
85            BinaryOp::Equal => Ok(order.map(Ordering::is_eq)),
86            BinaryOp::NotEqual => Ok(order.map(|order| !order.is_eq())),
87            BinaryOp::Less => Ok(order.map(Ordering::is_lt)),
88            BinaryOp::LessEqual => Ok(order.map(Ordering::is_le)),
89            BinaryOp::Greater => Ok(order.map(Ordering::is_gt)),
90            BinaryOp::GreaterEqual => Ok(order.map(Ordering::is_ge)),
91            _ => Err(SQLError::Internal(format!(
92                "non-comparison operator {op:?} reached comparison evaluation"
93            ))),
94        };
95    }
96    let catalog = engine.map(crate::expr::value_catalog::EngineValueCatalog);
97    eval_comparison_truth_with_enum_catalog(
98        op,
99        left,
100        right,
101        control,
102        catalog
103            .as_ref()
104            .map(|catalog| catalog as &dyn crate::expr::SQLValueCatalog),
105        state(),
106    )
107}
108
109/// Observe scalar and nested enums through the caller's catalog, preserving private scalar/row call state, shared type support state and ordinary three-valued rules.
110pub fn eval_comparison_truth_with_enum_catalog(
111    op: BinaryOp,
112    left: &Value,
113    right: &Value,
114    control: &ProductionControl<'_>,
115    enums: Option<&dyn super::super::enums::EnumLabelCatalog>,
116    state: Option<&super::super::enums::EnumComparisonState>,
117) -> Result<Option<bool>> {
118    control.check()?;
119    if matches!(op, BinaryOp::Equal | BinaryOp::NotEqual) {
120        return values_equal_nullable_with_catalog(left, right, control, enums)
121            .map(|equal| equal.map(|equal| if op == BinaryOp::Equal { equal } else { !equal }));
122    }
123    if !matches!(
124        op,
125        BinaryOp::Less | BinaryOp::LessEqual | BinaryOp::Greater | BinaryOp::GreaterEqual
126    ) {
127        return Err(SQLError::Internal(format!(
128            "non-comparison operator {op:?} reached comparison evaluation"
129        )));
130    }
131    let order = compare_nullable_with_catalog(left, right, control, enums, state)?;
132    Ok(order.map(|order| match op {
133        BinaryOp::Less => order.is_lt(),
134        BinaryOp::LessEqual => order.is_le(),
135        BinaryOp::Greater => order.is_gt(),
136        BinaryOp::GreaterEqual => order.is_ge(),
137        _ => unreachable!("only comparison operators reach comparison evaluation"),
138    }))
139}
140
141/// Two-valued equality treats SQL UNKNOWN as no match for CASE, NULLIF and membership probes.
142#[cfg(test)]
143pub(in crate::expr) fn values_equal(a: &Value, b: &Value) -> Result<bool> {
144    Ok(values_equal_nullable(a, b)? == Some(true))
145}
146
147pub fn values_equal_with_control(
148    a: &Value,
149    b: &Value,
150    control: &ProductionControl<'_>,
151) -> Result<bool> {
152    Ok(values_equal_nullable_with_control(a, b, control)? == Some(true))
153}
154
155#[cfg(test)]
156pub(in crate::expr) fn values_equal_nullable(a: &Value, b: &Value) -> Result<Option<bool>> {
157    values_equal_nullable_with_control(a, b, &ProductionControl::uncontrolled())
158}
159
160pub fn values_equal_nullable_with_control(
161    a: &Value,
162    b: &Value,
163    control: &ProductionControl<'_>,
164) -> Result<Option<bool>> {
165    values_equal_nullable_with_catalog(a, b, control, None)
166}
167
168fn values_equal_nullable_with_catalog(
169    a: &Value,
170    b: &Value,
171    control: &ProductionControl<'_>,
172    enums: Option<&dyn super::super::enums::EnumLabelCatalog>,
173) -> Result<Option<bool>> {
174    control.check()?;
175    if let Some(value) = super::super::enums::eval_comparison(BinaryOp::Equal, a, b, enums, None)? {
176        return match value {
177            Value::Null => Ok(None),
178            Value::Bool(value) => Ok(Some(value)),
179            _ => unreachable!("equality returns boolean or NULL"),
180        };
181    }
182    if let Some(order) = super::super::datums::compare_jsonb_with_control(a, b, control)? {
183        return Ok(Some(order.is_eq()));
184    }
185    let equal = match (a, b) {
186        (Value::Null, _) | (_, Value::Null) => None,
187        (Value::Datum(datum), _) => {
188            return values_equal_nullable_with_catalog(
189                &*super::super::datums::read_with_value_catalog_and_control(datum, enums, control)?,
190                b,
191                control,
192                enums,
193            )
194        }
195        (_, Value::Datum(datum)) => {
196            return values_equal_nullable_with_catalog(
197                a,
198                &*super::super::datums::read_with_value_catalog_and_control(datum, enums, control)?,
199                control,
200                enums,
201            )
202        }
203        (Value::Temporal(x), Value::Str(y)) | (Value::Str(y), Value::Temporal(x)) => Some(
204            x.parse_same_kind_in_order_with_control(
205                y,
206                crate::expr::transaction_timestamp_or_clock(),
207                crate::expr::temporal_date_order(),
208                control,
209            )?
210            .is_some_and(|parsed| x.cmp(&parsed).is_eq()),
211        ),
212        (Value::FixedChar(x), Value::Str(y)) | (Value::Str(y), Value::FixedChar(x)) => {
213            Some(compare_fixed_text(x, y, control)?.is_eq())
214        }
215        // Anonymous rows use SQL three-valued equality: a definite mismatch wins over an earlier NULL field. Native arrays and stored records instead use total element equality through the value owner below.
216        (Value::Row(xs), Value::Row(ys)) => {
217            if xs.len() != ys.len() {
218                return Ok(Some(false));
219            }
220            let mut unknown = false;
221            for (x, y) in xs.iter().zip(ys) {
222                let equal = if matches!((x, y), (Value::Row(_), Value::Row(_))) {
223                    Some(equal_values(x, y, control, enums, false)?)
224                } else {
225                    values_equal_nullable_with_catalog(x, y, control, enums)?
226                };
227                match equal {
228                    Some(false) => return Ok(Some(false)),
229                    Some(true) => {}
230                    None => unknown = true,
231                }
232            }
233            if unknown {
234                None
235            } else {
236                Some(true)
237            }
238        }
239        _ => Some(equal_values(a, b, control, enums, false)?),
240    };
241    Ok(equal)
242}
243
244/// Compare with the existing two-valued selector convention that SQL UNKNOWN sorts as equal.
245pub fn compare_with_control(
246    a: &Value,
247    b: &Value,
248    control: &ProductionControl<'_>,
249) -> Result<Ordering> {
250    Ok(compare_nullable_with_control(a, b, control)?.unwrap_or(Ordering::Equal))
251}
252
253pub fn compare_nullable_with_control(
254    a: &Value,
255    b: &Value,
256    control: &ProductionControl<'_>,
257) -> Result<Option<Ordering>> {
258    compare_nullable_with_catalog(a, b, control, None, None)
259}
260
261fn compare_nullable_with_catalog(
262    a: &Value,
263    b: &Value,
264    control: &ProductionControl<'_>,
265    enums: Option<&dyn EnumLabelCatalog>,
266    state: Option<&EnumComparisonState>,
267) -> Result<Option<Ordering>> {
268    control.check()?;
269    if matches!(a, Value::Null) || matches!(b, Value::Null) {
270        return Ok(None);
271    }
272    if let Some(order) = super::super::enums::comparison_order(a, b, enums, state, false)? {
273        return Ok(Some(order));
274    }
275    if let Some(order) = super::super::datums::compare_jsonb_with_control(a, b, control)? {
276        return Ok(Some(order));
277    }
278    match (a, b) {
279        (Value::Null, _) | (_, Value::Null) => Ok(None),
280        (Value::Datum(datum), _) => compare_nullable_with_catalog(
281            &*super::super::datums::read_with_value_catalog_and_control(datum, enums, control)?,
282            b,
283            control,
284            enums,
285            state,
286        ),
287        (_, Value::Datum(datum)) => compare_nullable_with_catalog(
288            a,
289            &*super::super::datums::read_with_value_catalog_and_control(datum, enums, control)?,
290            control,
291            enums,
292            state,
293        ),
294        (
295            Value::Int(_) | Value::Float(_) | Value::Decimal(_),
296            Value::Int(_) | Value::Float(_) | Value::Decimal(_),
297        )
298        | (Value::Bool(_), Value::Decimal(_))
299        | (Value::Decimal(_), Value::Bool(_))
300        | (Value::Str(_), Value::Str(_))
301        | (Value::FixedChar(_), Value::FixedChar(_))
302        | (Value::JsonB(_), Value::JsonB(_))
303        | (Value::Temporal(_), Value::Temporal(_))
304        | (Value::Bool(_), Value::Bool(_))
305        | (Value::Array(_), Value::Array(_))
306        | (Value::LegacyVector(_), Value::LegacyVector(_))
307        | (Value::List(_), Value::List(_))
308        | (Value::Record(_), Value::Record(_) | Value::Row(_))
309        | (Value::Row(_), Value::Record(_))
310        | (Value::Enum(_), Value::Enum(_)) => Ok(Some(compare_sql_values(a, b, control, enums)?)),
311        (Value::FixedChar(x), Value::Str(y)) | (Value::Str(x), Value::FixedChar(y)) => {
312            Ok(Some(compare_fixed_text(x, y, control)?))
313        }
314        (Value::Temporal(x), Value::Str(y)) => x
315            .parse_same_kind_in_order_with_control(
316                y,
317                crate::expr::transaction_timestamp_or_clock(),
318                crate::expr::temporal_date_order(),
319                control,
320            )?
321            .map(|parsed| Some(x.cmp(&parsed)))
322            .ok_or_else(|| SQLError::TypeMismatch(format!("cannot compare {a:?} with {b:?}"))),
323        (Value::Str(x), Value::Temporal(y)) => y
324            .parse_same_kind_in_order_with_control(
325                x,
326                crate::expr::transaction_timestamp_or_clock(),
327                crate::expr::temporal_date_order(),
328                control,
329            )?
330            .map(|parsed| Some(parsed.cmp(y)))
331            .ok_or_else(|| SQLError::TypeMismatch(format!("cannot compare {a:?} with {b:?}"))),
332        // Ordering is lexicographic; reaching NULL before a definite comparison leaves it unknown.
333        (Value::Row(xs), Value::Row(ys)) => compare_anonymous_row(xs, ys, control, enums, state),
334        (lhs, rhs) => Err(SQLError::TypeMismatch(format!(
335            "cannot compare {lhs:?} with {rhs:?}"
336        ))),
337    }
338}
339
340fn compare_anonymous_row(
341    left: &[Value],
342    right: &[Value],
343    control: &ProductionControl<'_>,
344    enums: Option<&dyn EnumLabelCatalog>,
345    state: Option<&EnumComparisonState>,
346) -> Result<Option<Ordering>> {
347    for (index, (left, right)) in left.iter().zip(right).enumerate() {
348        let field =
349            if matches!(left, Value::Enum(_) | Value::Datum(_)) && !matches!(right, Value::Null) {
350                state.map(|state| state.field(index))
351            } else {
352                None
353            };
354        let order = if matches!((left, right), (Value::Row(_), Value::Row(_))) {
355            Some(compare_typed_values_with_catalog(
356                left, right, control, enums,
357            )?)
358        } else {
359            compare_nullable_with_catalog(left, right, control, enums, field.as_deref())?
360        };
361        match order {
362            Some(Ordering::Equal) => {}
363            other => return Ok(other),
364        }
365    }
366    Ok(Some(left.len().cmp(&right.len())))
367}
368
369fn compare_sql_values(
370    left: &Value,
371    right: &Value,
372    control: &ProductionControl<'_>,
373    enums: Option<&dyn EnumLabelCatalog>,
374) -> Result<Ordering> {
375    // Primitive mixed float/integer and float/numeric operators select float8 inputs. Physical keys and already-bound container elements keep their exact carrier order.
376    if matches!(
377        (left, right),
378        (Value::Float(_), Value::Int(_) | Value::Decimal(_))
379            | (Value::Int(_) | Value::Decimal(_), Value::Float(_))
380    ) {
381        let left =
382            super::super::cast_value_from_with_control(left, "double precision", None, control)?;
383        let right =
384            super::super::cast_value_from_with_control(right, "double precision", None, control)?;
385        return Ok(left.cmp(&right));
386    }
387    compare_typed_values_with_catalog(left, right, control, enums)
388}
389
390/// Compare already-bound values, preserving type operator failures and total container NULL semantics. Callers supply top-level NULL placement and must apply operator-selected casts first.
391pub fn compare_typed_values_with_control(
392    left: &Value,
393    right: &Value,
394    control: &ProductionControl<'_>,
395) -> Result<Ordering> {
396    compare_typed_values_with_catalog(left, right, control, None)
397}
398
399/// Compare one physical ordering key with its private scalar enum call state. Nested array/record fields use the catalog's shared type comparison state.
400pub fn compare_typed_values_with_enum_catalog(
401    left: &Value,
402    right: &Value,
403    control: &ProductionControl<'_>,
404    enums: Option<&dyn EnumLabelCatalog>,
405    state: Option<&EnumComparisonState>,
406) -> Result<Ordering> {
407    control.check()?;
408    if !matches!(left, Value::Null) && !matches!(right, Value::Null) {
409        if let Some(order) =
410            super::super::enums::comparison_order(left, right, enums, state, false)?
411        {
412            return Ok(order);
413        }
414    }
415    compare_typed_values_with_catalog(left, right, control, enums)
416}
417
418/// Order internal equality groups by physical enum identity, including nested keys. This ordering is not exposed as SQL ORDER BY and never observes an enum label merely to eliminate duplicates.
419pub fn compare_grouping_values_with_enum_catalog(
420    left: &Value,
421    right: &Value,
422    control: &ProductionControl<'_>,
423    enums: Option<&dyn EnumLabelCatalog>,
424) -> Result<Ordering> {
425    compare_typed_values_for_purpose(left, right, control, enums, EnumKeyOrder::Identity)
426}
427
428#[derive(Clone, Copy)]
429enum EnumKeyOrder {
430    Declaration,
431    Identity,
432}
433
434fn enum_key_order(
435    left: &Value,
436    right: &Value,
437    enums: Option<&dyn EnumLabelCatalog>,
438    order: EnumKeyOrder,
439) -> Result<Option<Ordering>> {
440    if matches!(order, EnumKeyOrder::Identity) {
441        if let Some(enums) = enums {
442            if let (Some(left), Some(right)) = (
443                super::super::enums::comparison_identity(enums, left)?,
444                super::super::enums::comparison_identity(enums, right)?,
445            ) {
446                return Ok(Some(left.cmp(&right)));
447            }
448        }
449        return Ok(None);
450    }
451    super::super::enums::comparison_order(left, right, enums, None, true)
452}
453
454fn compare_typed_values_with_catalog(
455    left: &Value,
456    right: &Value,
457    control: &ProductionControl<'_>,
458    enums: Option<&dyn EnumLabelCatalog>,
459) -> Result<Ordering> {
460    compare_typed_values_for_purpose(left, right, control, enums, EnumKeyOrder::Declaration)
461}
462
463fn compare_typed_values_for_purpose(
464    left: &Value,
465    right: &Value,
466    control: &ProductionControl<'_>,
467    enums: Option<&dyn EnumLabelCatalog>,
468    enum_order: EnumKeyOrder,
469) -> Result<Ordering> {
470    control.check()?;
471    if let Some(order) = super::super::datums::compare_jsonb_with_control(left, right, control)? {
472        return Ok(order);
473    }
474    match (left, right) {
475        (Value::Null, Value::Null) => return Ok(Ordering::Equal),
476        (Value::Null, _) => return Ok(Ordering::Greater),
477        (_, Value::Null) => return Ok(Ordering::Less),
478        _ => {}
479    }
480    if let Some(order) = enum_key_order(left, right, enums, enum_order)? {
481        return Ok(order);
482    }
483    match (left, right) {
484        (Value::Datum(datum), _) => {
485            return compare_typed_values_for_purpose(
486                &*super::super::datums::read_with_value_catalog_and_control(datum, enums, control)?,
487                right,
488                control,
489                enums,
490                enum_order,
491            )
492        }
493        (_, Value::Datum(datum)) => {
494            return compare_typed_values_for_purpose(
495                left,
496                &*super::super::datums::read_with_value_catalog_and_control(datum, enums, control)?,
497                control,
498                enums,
499                enum_order,
500            )
501        }
502        (Value::Array(left), Value::Array(right)) => {
503            validate_array_element_types(left, right)?;
504            return left.cmp_by_with_control(right, control, |left, right, control| {
505                compare_typed_values_for_purpose(left, right, control, enums, enum_order)
506            });
507        }
508        (Value::Record(_) | Value::Row(_), Value::Record(_) | Value::Row(_)) => {
509            return records::compare(
510                left,
511                right,
512                enums,
513                control,
514                Ordering::Equal,
515                |left, right| {
516                    let order =
517                        compare_typed_values_for_purpose(left, right, control, enums, enum_order)?;
518                    Ok((!order.is_eq()).then_some(order))
519                },
520            );
521        }
522        (Value::List(left), Value::List(right)) => {
523            return compare_sequence(left.iter(), right.iter(), control, enums, enum_order);
524        }
525        // Enum operators are declared on one enum type; binding coerces every other operand to it.
526        (Value::Enum(left), Value::Enum(right)) if left.type_oid() == right.type_oid() => {
527            return Ok(left.key().cmp(right.key()));
528        }
529        (Value::Enum(_), _) | (_, Value::Enum(_)) => {
530            return Err(SQLError::Internal(format!(
531                "enum comparison reached operands of different types: {} and {}",
532                comparison_operand_type(left),
533                comparison_operand_type(right)
534            )));
535        }
536        _ => {}
537    }
538    for value in [left, right] {
539        if let Value::LegacyVector(vector) = value {
540            validate_legacy_vector_comparison(vector)?;
541        }
542    }
543    left.cmp_with_control(right, control).map_err(Into::into)
544}
545
546fn comparison_operand_type(value: &Value) -> String {
547    match value {
548        Value::Enum(label) => format!("enum type OID {}", label.type_oid()),
549        other => super::super::diagnostics::value_type_name(other).to_owned(),
550    }
551}
552
553/// Validate the layout required by the `oidvector` scalar equality, ordering and hashing operators. Array operators on `int2vector` permit dimensionless arrays.
554pub fn validate_legacy_vector_comparison(vector: &uqa_core::LegacyVectorValue) -> Result<()> {
555    if vector.kind() == uqa_core::LegacyVectorKind::Oid && !vector.has_vector_layout() {
556        return Err(SQLError::Routine {
557            sqlstate: "42804".into(),
558            message: "array is not a valid oidvector".into(),
559        });
560    }
561    Ok(())
562}
563
564/// Declared SQL keys whose runtime values may require a fallible comparison operator.
565pub fn type_comparison_can_fail(ty: &crate::ast::ColumnType) -> bool {
566    use crate::ast::ColumnType;
567    match ty {
568        ColumnType::OidVector
569        | ColumnType::Record
570        | ColumnType::Composite(_)
571        | ColumnType::Array(_) => true,
572        ColumnType::Domain { base: element, .. } => type_comparison_can_fail(element),
573        _ => false,
574    }
575}
576
577/// Whether an opaque value key can suppress a SQL operator failure. A single such input remains legal until an operator compares it.
578pub fn value_comparison_can_fail(value: &Value) -> bool {
579    match value {
580        Value::Datum(_) => true,
581        Value::LegacyVector(vector) => {
582            vector.kind() == uqa_core::LegacyVectorKind::Oid && !vector.has_vector_layout()
583        }
584        Value::Array(array) => {
585            array.element_type_oid().is_some()
586                || array.elements().iter().any(value_comparison_can_fail)
587        }
588        Value::Row(values) => values.iter().any(value_comparison_can_fail),
589        Value::List(values) => values.iter().any(value_comparison_can_fail),
590        Value::Record(fields) => {
591            fields.type_oid().is_some()
592                || fields
593                    .iter()
594                    .any(|(_, value)| value_comparison_can_fail(value))
595        }
596        _ => false,
597    }
598}
599
600/// Compare already-bound group/hash keys without enum output. Physical enum
601/// identities use the same equality as `enum_eq`, including inside containers.
602pub fn equal_typed_values_with_enum_catalog(
603    left: &Value,
604    right: &Value,
605    control: &ProductionControl<'_>,
606    enums: Option<&dyn super::super::enums::EnumLabelCatalog>,
607) -> Result<bool> {
608    equal_values(left, right, control, enums, true)
609}
610
611fn equal_values(
612    left: &Value,
613    right: &Value,
614    control: &ProductionControl<'_>,
615    enums: Option<&dyn super::super::enums::EnumLabelCatalog>,
616    typed: bool,
617) -> Result<bool> {
618    control.check()?;
619    if let Some(order) = super::super::datums::compare_jsonb_with_control(left, right, control)? {
620        return Ok(order.is_eq());
621    }
622    if matches!(left, Value::Null) || matches!(right, Value::Null) {
623        return Ok(matches!((left, right), (Value::Null, Value::Null)));
624    }
625    if let Some(enums) = enums {
626        if let (Some(left), Some(right)) = (
627            super::super::enums::comparison_identity(enums, left)?,
628            super::super::enums::comparison_identity(enums, right)?,
629        ) {
630            return Ok(left == right);
631        }
632    }
633    match (left, right) {
634        (Value::Datum(datum), _) => equal_values(
635            &*super::super::datums::read_with_value_catalog_and_control(datum, enums, control)?,
636            right,
637            control,
638            enums,
639            typed,
640        ),
641        (_, Value::Datum(datum)) => equal_values(
642            left,
643            &*super::super::datums::read_with_value_catalog_and_control(datum, enums, control)?,
644            control,
645            enums,
646            typed,
647        ),
648        (Value::Array(left), Value::Array(right)) => {
649            validate_array_element_types(left, right)?;
650            left.eq_by_with_control(right, control, |left, right, control| {
651                equal_values(left, right, control, enums, typed)
652            })
653        }
654        (Value::Record(_) | Value::Row(_), Value::Record(_) | Value::Row(_)) => {
655            records::compare(left, right, enums, control, true, |left, right| {
656                Ok((!equal_values(left, right, control, enums, typed)?).then_some(false))
657            })
658        }
659        (Value::List(left), Value::List(right)) => {
660            equal_sequence(left.iter(), right.iter(), control, enums, typed)
661        }
662        _ if typed => Ok(compare_typed_values_with_control(left, right, control)?.is_eq()),
663        _ => Ok(compare_sql_values(left, right, control, enums)?.is_eq()),
664    }
665}
666
667fn validate_array_element_types(
668    left: &uqa_core::ArrayValue,
669    right: &uqa_core::ArrayValue,
670) -> Result<()> {
671    if matches!((left.element_type_oid(), right.element_type_oid()), (Some(left), Some(right)) if left != right)
672    {
673        return Err(SQLError::Routine {
674            sqlstate: "42804".into(),
675            message: "cannot compare arrays of different element types".into(),
676        });
677    }
678    Ok(())
679}
680
681fn equal_sequence<'a>(
682    mut left: impl Iterator<Item = &'a Value>,
683    mut right: impl Iterator<Item = &'a Value>,
684    control: &ProductionControl<'_>,
685    enums: Option<&dyn super::super::enums::EnumLabelCatalog>,
686    typed: bool,
687) -> Result<bool> {
688    loop {
689        control.check()?;
690        match (left.next(), right.next()) {
691            (Some(left), Some(right)) if equal_values(left, right, control, enums, typed)? => {}
692            (None, None) => return Ok(true),
693            _ => return Ok(false),
694        }
695    }
696}
697
698fn compare_sequence<'a>(
699    mut left: impl Iterator<Item = &'a Value>,
700    mut right: impl Iterator<Item = &'a Value>,
701    control: &ProductionControl<'_>,
702    enums: Option<&dyn EnumLabelCatalog>,
703    enum_order: EnumKeyOrder,
704) -> Result<Ordering> {
705    loop {
706        control.check()?;
707        let ordering = match (left.next(), right.next()) {
708            (Some(left), Some(right)) => {
709                compare_typed_values_for_purpose(left, right, control, enums, enum_order)?
710            }
711            (Some(_), None) => Ordering::Greater,
712            (None, Some(_)) => Ordering::Less,
713            (None, None) => return Ok(Ordering::Equal),
714        };
715        if !ordering.is_eq() {
716            return Ok(ordering);
717        }
718    }
719}
720
721fn compare_fixed_text(
722    left: &str,
723    right: &str,
724    control: &ProductionControl<'_>,
725) -> Result<Ordering> {
726    fn trim<'a>(text: &'a str, control: &ProductionControl<'_>) -> Result<&'a [u8]> {
727        let mut bytes = text.as_bytes();
728        let mut checked = 0;
729        while bytes.last() == Some(&b' ') {
730            if checked % 4096 == 0 {
731                control.check()?;
732            }
733            bytes = &bytes[..bytes.len() - 1];
734            checked += 1;
735        }
736        Ok(bytes)
737    }
738    let left = trim(left, control)?;
739    let right = trim(right, control)?;
740    for (left, right) in left.chunks(4096).zip(right.chunks(4096)) {
741        control.check()?;
742        let ordering = left.cmp(right);
743        if !ordering.is_eq() {
744            return Ok(ordering);
745        }
746    }
747    control.check()?;
748    Ok(left.len().cmp(&right.len()))
749}
750
751#[cfg(test)]
752mod tests;