Skip to main content

uqa_sql/expr/
array_access.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Evaluate array access once, retaining `PostgreSQL`'s ordered bound effects.
8
9use std::ops::Deref;
10use uqa_core::{
11    memory::{Produced, ProductionControl},
12    Value,
13};
14
15use crate::{
16    ast::{ArrayAccess, MAX_ARRAY_SUBSCRIPT_DIMENSIONS},
17    SQLError,
18};
19
20/// The caller owns expression scheduling and input guards; SQL owns NULL reachability, bound conversion and selected array output.
21pub fn eval_array_access_with_control(
22    access: ArrayAccess,
23    control: &ProductionControl<'_>,
24    mut evaluate: impl FnMut(usize) -> Result<Produced<Value>, SQLError>,
25) -> Result<Produced<Value>, SQLError> {
26    control.check()?;
27    access.validate_dimensions()?;
28    let array = evaluate(0)?;
29    eval_array_access_from_input(access, &array, control, evaluate)
30}
31
32/// Already evaluated inputs stay borrowed; produced inputs retain their original guards until each conversion finishes.
33pub(in crate::expr) fn eval_array_access_from_input<B: Deref<Target = Value>>(
34    access: ArrayAccess,
35    array: &Value,
36    control: &ProductionControl<'_>,
37    mut evaluate: impl FnMut(usize) -> Result<B, SQLError>,
38) -> Result<Produced<Value>, SQLError> {
39    control.check()?;
40    access.validate_dimensions()?;
41    if matches!(array, Value::Null) {
42        return Ok(control.finish(Value::Null, control.empty_reservation())?);
43    }
44    let mut bounds: [Value; MAX_ARRAY_SUBSCRIPT_DIMENSIONS * 2] =
45        std::array::from_fn(|_| Value::Null);
46    for index in access.evaluation_order() {
47        control.check()?;
48        let value = evaluate(index)?;
49        let value = super::cast_value_from_with_control(&value, "integer", None, control)?;
50        bounds[index - 1] = match *value {
51            Value::Null => Value::Null,
52            Value::Int(value) => Value::Int(value),
53            _ => {
54                return Err(SQLError::Internal(
55                    "array bound coercion did not produce an integer".into(),
56                ))
57            }
58        };
59    }
60    super::scalar_postgres::subscripts::eval_selected_array_access(
61        array,
62        &bounds[..access.bound_count()],
63        access.slice_bounds(),
64        control,
65    )
66}
67
68#[cfg(test)]
69mod tests;