Skip to main content

uqa_sql/expr/composites/
literal.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Read an already admitted constant through the current descriptor. Added fields are NULL even when their domains reject fresh NULL inputs; the retained value and its input expression remain unchanged.
8
9use crate::{expr::EngineHook, ColumnType, SQLError};
10use uqa_core::{
11    memory::{Produced, ProductionControl, ProductionVec},
12    ArrayValue, Value,
13};
14
15pub fn contains_records(value: &Value) -> bool {
16    inspect_records(value, &mut || Ok(())).expect("uncontrolled value inspection")
17}
18
19fn inspect_records(
20    value: &Value,
21    check: &mut impl FnMut() -> Result<(), SQLError>,
22) -> Result<bool, SQLError> {
23    check()?;
24    let values = match value {
25        Value::Record(_) => return Ok(true),
26        Value::Array(array) => array.elements(),
27        Value::List(values) => values.as_slice(),
28        _ => return Ok(false),
29    };
30    for value in values {
31        if inspect_records(value, check)? {
32            return Ok(true);
33        }
34    }
35    Ok(false)
36}
37
38pub fn evaluate_with_control(
39    value: &Value,
40    ty: &str,
41    engine: Option<&dyn EngineHook>,
42    control: &ProductionControl<'_>,
43) -> Result<Produced<Value>, SQLError> {
44    control.check()?;
45    if !inspect_records(value, &mut || control.check().map_err(Into::into))? {
46        return control.copy_value(value).map_err(Into::into);
47    }
48    let Some(engine) = engine else {
49        return control.copy_value(value).map_err(Into::into);
50    };
51    let ty = engine
52        .resolve_type_name(ty)
53        .map_err(SQLError::Internal)?
54        .map(|ty| ty.retain_external_with_control(control))
55        .transpose()?;
56    control.check()?;
57    match ty.as_deref() {
58        Some(ty) => materialize(value, ty, engine, control),
59        None => control.copy_value(value).map_err(Into::into),
60    }
61}
62
63fn materialize(
64    value: &Value,
65    ty: &ColumnType,
66    engine: &dyn EngineHook,
67    control: &ProductionControl<'_>,
68) -> Result<Produced<Value>, SQLError> {
69    control.check()?;
70    match (ty, value) {
71        (ColumnType::Domain { base, .. }, _) => materialize(value, base, engine, control),
72        (ColumnType::Composite(reference), Value::Record(fields)) => {
73            let descriptor = super::descriptor(engine.composite_types(), reference.oid)?;
74            control.check()?;
75            let mut output = ProductionVec::new(*control);
76            output.reserve(descriptor.attributes.len())?;
77            for (index, attribute) in descriptor.attributes.iter().enumerate() {
78                let value = fields
79                    .get(index)
80                    .filter(|(name, _)| *name == attribute.name)
81                    .or_else(|| fields.iter().find(|(name, _)| *name == attribute.name))
82                    .map_or(&Value::Null, |(_, value)| value);
83                let value = materialize(value, &attribute.ty, engine, control)?;
84                let name = control.copy_text(&attribute.name)?;
85                let (value, memory) = value.into_parts();
86                let (name, name_memory) = name.into_parts();
87                output.push_produced(
88                    control.finish((name, value), control.combine(memory, name_memory))?,
89                )?;
90            }
91            let (fields, memory) = output.finish()?.into_parts();
92            control
93                .finish(Value::Record(fields), memory)
94                .map_err(Into::into)
95        }
96        (ColumnType::Array(element), Value::Array(array)) => {
97            let elements = array_elements(array.elements(), element, engine, control)?;
98            let mut bounds = ProductionVec::new(*control);
99            for bound in array.lower_bounds() {
100                bounds.push_copy(*bound)?;
101            }
102            let array =
103                ArrayValue::with_lower_bounds_with_control(elements, bounds.finish()?, control)?
104                    .ok_or_else(|| {
105                        SQLError::Internal(
106                            "retained composite constant changed array dimensions".into(),
107                        )
108                    })?;
109            let (array, memory) = array.into_parts();
110            control
111                .finish(Value::Array(array), memory)
112                .map_err(Into::into)
113        }
114        _ => control.copy_value(value).map_err(Into::into),
115    }
116}
117
118fn array_elements(
119    values: &[Value],
120    element: &ColumnType,
121    engine: &dyn EngineHook,
122    control: &ProductionControl<'_>,
123) -> Result<Produced<Vec<Value>>, SQLError> {
124    let mut element = element;
125    while let ColumnType::Array(inner) = element {
126        element = inner;
127    }
128    let mut output = ProductionVec::new(*control);
129    output.reserve(values.len())?;
130    for value in values {
131        let value = if let Value::List(values) = value {
132            let (values, memory) = array_elements(values, element, engine, control)?.into_parts();
133            control.finish(Value::List(values), memory)?
134        } else {
135            materialize(value, element, engine, control)?
136        };
137        output.push_produced(value)?;
138    }
139    output.finish().map_err(Into::into)
140}
141
142#[cfg(test)]
143mod tests;