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 type_oid = fields.type_oid().unwrap_or(reference.oid);
74            let descriptor = super::descriptor(engine.composite_types(), type_oid)?;
75            control.check()?;
76            let mut output = ProductionVec::new(*control);
77            output.reserve(descriptor.attributes.len())?;
78            for (index, attribute) in descriptor.attributes.iter().enumerate() {
79                let value = fields
80                    .get(index)
81                    .filter(|(name, _)| *name == attribute.name)
82                    .or_else(|| fields.iter().find(|(name, _)| *name == attribute.name))
83                    .map_or(&Value::Null, |(_, value)| value);
84                let value = materialize(value, &attribute.ty, engine, control)?;
85                let name = control.copy_text(&attribute.name)?;
86                let (value, memory) = value.into_parts();
87                let (name, name_memory) = name.into_parts();
88                output.push_produced(
89                    control.finish((name, value), control.combine(memory, name_memory))?,
90                )?;
91            }
92            let (fields, memory) =
93                uqa_core::RecordValue::with_control(output.finish()?, Some(type_oid), control)?
94                    .into_parts();
95            control
96                .finish(Value::Record(fields), memory)
97                .map_err(Into::into)
98        }
99        (ColumnType::Array(element), Value::Array(array)) => {
100            let element_type_oid = array.element_type_oid();
101            let elements = array_elements(array.elements(), element, engine, control)?;
102            let mut bounds = ProductionVec::new(*control);
103            for bound in array.lower_bounds() {
104                bounds.push_copy(*bound)?;
105            }
106            let array =
107                ArrayValue::with_lower_bounds_with_control(elements, bounds.finish()?, control)?
108                    .ok_or_else(|| {
109                        SQLError::Internal(
110                            "retained composite constant changed array dimensions".into(),
111                        )
112                    })?;
113            let (array, memory) = array.into_parts();
114            control
115                .finish(
116                    Value::Array(array.with_element_type_oid(element_type_oid)),
117                    memory,
118                )
119                .map_err(Into::into)
120        }
121        _ => control.copy_value(value).map_err(Into::into),
122    }
123}
124
125fn array_elements(
126    values: &[Value],
127    element: &ColumnType,
128    engine: &dyn EngineHook,
129    control: &ProductionControl<'_>,
130) -> Result<Produced<Vec<Value>>, SQLError> {
131    let mut element = element;
132    while let ColumnType::Array(inner) = element {
133        element = inner;
134    }
135    let mut output = ProductionVec::new(*control);
136    output.reserve(values.len())?;
137    for value in values {
138        let value = if let Value::List(values) = value {
139            let (values, memory) = array_elements(values, element, engine, control)?.into_parts();
140            control.finish(Value::List(values), memory)?
141        } else {
142            materialize(value, element, engine, control)?
143        };
144        output.push_produced(value)?;
145    }
146    output.finish().map_err(Into::into)
147}
148
149#[cfg(test)]
150mod tests;