Skip to main content

uqa_sql/assignment/conversion/
carrier.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Type-directed normalization of predecessor legacy-vector carriers without repeating domain constraints.
8
9use crate::{ColumnType, SQLError};
10use uqa_core::{
11    memory::{Produced, ProductionControl, ProductionVec},
12    ArrayValue, LegacyVectorKind, Value,
13};
14
15/// Whether this declared type contains catalog vectors, including domain and array wrappers.
16pub fn contains_legacy_vectors(ty: &ColumnType) -> bool {
17    element_kind(ty).is_some()
18}
19
20/// Return a replacement only when the declared type contains legacy vectors whose runtime representation is not canonical. Domain checks have already run when the stored value was admitted; normalization changes its carrier, not its domain membership.
21pub fn normalize_legacy_vector_carrier_with_control(
22    value: &Value,
23    ty: &ColumnType,
24    control: &ProductionControl<'_>,
25) -> Result<Option<Produced<Value>>, SQLError> {
26    control.check()?;
27    if matches!(value, Value::Null) {
28        return Ok(None);
29    }
30    match ty {
31        ColumnType::Domain { base, .. } => {
32            normalize_legacy_vector_carrier_with_control(value, base, control)
33        }
34        ColumnType::Int2Vector | ColumnType::OidVector => {
35            let name = super::column_type_name(ty);
36            if matches!(value, Value::LegacyVector(vector) if vector.kind().type_name() == name) {
37                return Ok(None);
38            }
39            crate::expr::cast_value_from_with_control(value, name, Some(name), control).map(Some)
40        }
41        ColumnType::Array(element) => {
42            let Some(kind) = element_kind(element) else {
43                return Ok(None);
44            };
45            let (elements, bounds) = match value {
46                Value::Array(array) => (array.elements(), Some(array.lower_bounds())),
47                Value::List(elements) => (elements.as_slice(), None),
48                _ => return Err(invalid_array()),
49            };
50            if bounds.is_some() && canonical_elements(elements, kind, control)? {
51                return Ok(None);
52            }
53            let (elements, collapsed) = normalize_elements(elements, kind, control)?;
54            let array = if let Some(bounds) = bounds {
55                let count = bounds
56                    .len()
57                    .checked_sub(usize::from(collapsed))
58                    .ok_or_else(invalid_array)?;
59                let mut output = ProductionVec::new(*control);
60                for bound in &bounds[..count] {
61                    output.push_copy(*bound)?;
62                }
63                ArrayValue::with_lower_bounds_with_control(elements, output.finish()?, control)?
64            } else {
65                ArrayValue::try_new_with_control(elements, control)?
66            }
67            .ok_or_else(invalid_array)?;
68            let (array, memory) = array.into_parts();
69            let oid = value.array_view().and_then(ArrayValue::element_type_oid);
70            Ok(Some(control.finish(
71                Value::Array(array.with_element_type_oid(oid)),
72                memory,
73            )?))
74        }
75        _ => Ok(None),
76    }
77}
78
79fn element_kind(ty: &ColumnType) -> Option<LegacyVectorKind> {
80    match ty {
81        ColumnType::Domain { base, .. } | ColumnType::Array(base) => element_kind(base),
82        ColumnType::Int2Vector => Some(LegacyVectorKind::SmallInteger),
83        ColumnType::OidVector => Some(LegacyVectorKind::Oid),
84        _ => None,
85    }
86}
87
88fn canonical_elements(
89    values: &[Value],
90    kind: LegacyVectorKind,
91    control: &ProductionControl<'_>,
92) -> Result<bool, SQLError> {
93    for value in values {
94        control.check()?;
95        match value {
96            Value::Null => {}
97            Value::LegacyVector(vector) if vector.kind() == kind => {}
98            Value::List(nested)
99                if !nested.is_empty() && canonical_elements(nested, kind, control)? => {}
100            _ => return Ok(false),
101        }
102    }
103    Ok(true)
104}
105
106fn normalize_elements(
107    values: &[Value],
108    kind: LegacyVectorKind,
109    control: &ProductionControl<'_>,
110) -> Result<(Produced<Vec<Value>>, bool), SQLError> {
111    let mut output = ProductionVec::new(*control);
112    output.reserve(values.len())?;
113    let mut collapsed = false;
114    for value in values {
115        control.check()?;
116        let value = match value {
117            Value::Null => control.copy_value(value)?,
118            Value::List(nested) => {
119                let mut flat = true;
120                for item in nested {
121                    control.check()?;
122                    flat &= matches!(item, Value::Int(_));
123                }
124                if flat {
125                    collapsed = true;
126                    crate::expr::cast_value_from_with_control(
127                        value,
128                        kind.type_name(),
129                        Some(kind.type_name()),
130                        control,
131                    )?
132                } else {
133                    let (values, child_collapsed) = normalize_elements(nested, kind, control)?;
134                    collapsed |= child_collapsed;
135                    let (values, memory) = values.into_parts();
136                    control.finish(Value::List(values), memory)?
137                }
138            }
139            _ => crate::expr::cast_value_from_with_control(
140                value,
141                kind.type_name(),
142                Some(kind.type_name()),
143                control,
144            )?,
145        };
146        output.push_produced(value)?;
147    }
148    Ok((output.finish()?, collapsed))
149}
150
151fn invalid_array() -> SQLError {
152    SQLError::TypeMismatch("invalid stored array of legacy vectors".into())
153}
154
155#[cfg(test)]
156mod tests;
157
158pub(in crate::assignment) fn normalize_existing(
159    value: Value,
160    ty: &ColumnType,
161) -> Result<Value, SQLError> {
162    normalize_legacy_vector_carrier_with_control(&value, ty, &ProductionControl::uncontrolled())?
163        .map_or(Ok(value), |value| {
164            value
165                .into_uncontrolled()
166                .map_err(|_| SQLError::Internal("ordinary carrier normalization".into()))
167        })
168}