Skip to main content

uqa_sql/render/
legacy_vector.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Reconstruct SQL-produced catalog vectors without discarding their array metadata.
8
9use crate::SQLError;
10use uqa_core::{LegacyVectorValue, Value};
11
12/// Render an expression retaining the vector's kind, dimensions and bounds. SQL array functions can produce dimensionless results whose text output function rejects them.
13pub fn legacy_vector_expression(vector: &LegacyVectorValue) -> Result<String, SQLError> {
14    let ty = vector.kind().type_name();
15    if !vector.has_vector_layout() {
16        return Ok(format!("trim_array('0'::{ty}, 1)"));
17    }
18    let text = vector
19        .elements()
20        .iter()
21        .map(|value| {
22            let Value::Int(value) = value else {
23                unreachable!("validated integer vector")
24            };
25            value.to_string()
26        })
27        .collect::<Vec<_>>()
28        .join(" ");
29    let literal = format!("'{text}'::{ty}");
30    match vector.as_array().lower_bounds() {
31        [0] => Ok(literal),
32        [1] if !vector.elements().is_empty() => Ok(format!("trim_array({literal}, 0)")),
33        _ => Err(SQLError::TypeMismatch(
34            "legacy vector lower bounds cannot be represented by an SQL literal".into(),
35        )),
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use uqa_core::{ArrayValue, LegacyVectorKind};
43
44    #[test]
45    fn legacy_vector_expression_round_trip_preserves_dimensions_and_bounds() {
46        for kind in [LegacyVectorKind::SmallInteger, LegacyVectorKind::Oid] {
47            for (elements, bounds) in [
48                (vec![Value::Int(1)], vec![0]),
49                (vec![Value::Int(1)], vec![1]),
50                (vec![], vec![0]),
51                (vec![], vec![]),
52            ] {
53                let vector = LegacyVectorValue::try_from_array(
54                    kind,
55                    ArrayValue::with_lower_bounds(elements, bounds).unwrap(),
56                )
57                .unwrap();
58                let expression = legacy_vector_expression(&vector).unwrap();
59                let crate::Statement::Select(select) =
60                    crate::compile(&format!("SELECT {expression}"))
61                        .unwrap()
62                        .remove(0)
63                else {
64                    panic!("expected SELECT")
65                };
66                let value = crate::expr::eval(
67                    &select.projections[0].expr,
68                    &crate::expr::EvalContext::new(None, &[]),
69                )
70                .unwrap();
71                let Value::LegacyVector(restored) = value else {
72                    panic!("lost vector type")
73                };
74                assert_eq!(restored.kind(), kind);
75                assert_eq!(restored.as_array(), vector.as_array());
76                assert_eq!(
77                    crate::catalog::expression_text::schema_expr_text(&crate::ast::Expr::Literal(
78                        Value::LegacyVector(vector)
79                    )),
80                    expression
81                );
82            }
83        }
84    }
85}