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            Ok(Some(control.finish(Value::Array(array), memory)?))
70        }
71        _ => Ok(None),
72    }
73}
74
75fn element_kind(ty: &ColumnType) -> Option<LegacyVectorKind> {
76    match ty {
77        ColumnType::Domain { base, .. } | ColumnType::Array(base) => element_kind(base),
78        ColumnType::Int2Vector => Some(LegacyVectorKind::SmallInteger),
79        ColumnType::OidVector => Some(LegacyVectorKind::Oid),
80        _ => None,
81    }
82}
83
84fn canonical_elements(
85    values: &[Value],
86    kind: LegacyVectorKind,
87    control: &ProductionControl<'_>,
88) -> Result<bool, SQLError> {
89    for value in values {
90        control.check()?;
91        match value {
92            Value::Null => {}
93            Value::LegacyVector(vector) if vector.kind() == kind => {}
94            Value::List(nested)
95                if !nested.is_empty() && canonical_elements(nested, kind, control)? => {}
96            _ => return Ok(false),
97        }
98    }
99    Ok(true)
100}
101
102fn normalize_elements(
103    values: &[Value],
104    kind: LegacyVectorKind,
105    control: &ProductionControl<'_>,
106) -> Result<(Produced<Vec<Value>>, bool), SQLError> {
107    let mut output = ProductionVec::new(*control);
108    output.reserve(values.len())?;
109    let mut collapsed = false;
110    for value in values {
111        control.check()?;
112        let value = match value {
113            Value::Null => control.copy_value(value)?,
114            Value::List(nested) => {
115                let mut flat = true;
116                for item in nested {
117                    control.check()?;
118                    flat &= matches!(item, Value::Int(_));
119                }
120                if flat {
121                    collapsed = true;
122                    crate::expr::cast_value_from_with_control(
123                        value,
124                        kind.type_name(),
125                        Some(kind.type_name()),
126                        control,
127                    )?
128                } else {
129                    let (values, child_collapsed) = normalize_elements(nested, kind, control)?;
130                    collapsed |= child_collapsed;
131                    let (values, memory) = values.into_parts();
132                    control.finish(Value::List(values), memory)?
133                }
134            }
135            _ => crate::expr::cast_value_from_with_control(
136                value,
137                kind.type_name(),
138                Some(kind.type_name()),
139                control,
140            )?,
141        };
142        output.push_produced(value)?;
143    }
144    Ok((output.finish()?, collapsed))
145}
146
147fn invalid_array() -> SQLError {
148    SQLError::TypeMismatch("invalid stored array of legacy vectors".into())
149}
150
151#[cfg(test)]
152mod tests;
153
154pub(in crate::assignment) fn normalize_existing(
155    value: Value,
156    ty: &ColumnType,
157) -> Result<Value, SQLError> {
158    normalize_legacy_vector_carrier_with_control(&value, ty, &ProductionControl::uncontrolled())?
159        .map_or(Ok(value), |value| {
160            value
161                .into_uncontrolled()
162                .map_err(|_| SQLError::Internal("ordinary carrier normalization".into()))
163        })
164}