uqa-sql 0.5.3

PostgreSQL-compatible SQL compiler built on libpg_query
Documentation
//
// Unified Query Algebra
//
// Copyright (c) 2023-2026 Cognica, Inc.
//

use super::*;
use crate::ast::{CompositeTypeReference, FunctionBinding};
use crate::expr::composites::{CompositeAttribute, CompositeTypeDescriptor};
use std::sync::Arc;
use uqa_core::ArrayValue;

struct Catalog;

fn pair_type() -> ColumnType {
    ColumnType::Composite(CompositeTypeReference {
        schema: "public".into(),
        name: "pair".into(),
        oid: 20_001,
        array_oid: 20_002,
        relation_oid: 20_003,
    })
}

impl CompositeTypeCatalog for Catalog {
    fn composite_type(&self, oid: u32) -> Result<Option<Arc<CompositeTypeDescriptor>>, SQLError> {
        Ok((oid == 20_001).then(|| {
            Arc::new(CompositeTypeDescriptor {
                dropped: Vec::new(),
                type_oid: oid,
                relation_oid: 20_003,
                attributes: vec![CompositeAttribute {
                    name: "a".into(),
                    ty: ColumnType::Integer,
                    number: 1,
                }],
            })
        }))
    }
}

impl FunctionTypeResolver for Catalog {
    fn resolve_type_name(&self, name: &str) -> Result<Option<ColumnType>, SQLError> {
        let pair = pair_type();
        let array = ColumnType::Array(Box::new(pair.clone()));
        Ok([pair, array]
            .into_iter()
            .find(|ty| ty.catalog_name() == name))
    }

    fn resolve_function_type(
        &self,
        _: &str,
        _: Option<&FunctionBinding>,
        _: &[Option<String>],
        _: &[Option<ColumnType>],
        _: bool,
    ) -> Result<Option<ColumnType>, SQLError> {
        panic!("rewriting a stored datum must not bind or evaluate functions")
    }
}

fn change(change: &AttributeChange) -> CompositeConstantChange<'_> {
    CompositeConstantChange {
        target: 20_001,
        change,
        catalog: &Catalog,
        types: &Catalog,
        rename: None,
    }
}

fn value() -> Value {
    Value::Record(vec![("a".into(), Value::Int(4))].into())
}

#[test]
fn stored_input_datums_reinterpret_bits_without_rebinding_or_reapplying_input() {
    let array = ColumnType::Array(Box::new(pair_type()));
    let mut value = Value::Array(
        ArrayValue::with_lower_bounds(
            vec![
                Value::Record(vec![("a".into(), Value::Int(1_065_353_216))].into()),
                Value::Null,
            ],
            vec![-2],
        )
        .unwrap(),
    );
    let type_change = AttributeChange::Type {
        name: "a".into(),
        from: Box::new(ColumnType::Integer),
        to: Box::new(ColumnType::Real),
    };
    assert!(change(&type_change).value(&mut value, &array).unwrap());
    let Value::Array(value) = value else {
        panic!("array carrier")
    };
    assert_eq!(value.lower_bounds(), &[-2]);
    assert_eq!(
        value.elements(),
        &[
            Value::Record(vec![("a".into(), Value::Float(1.0))].into()),
            Value::Null,
        ]
    );
}

#[test]
fn typed_datums_expand_inside_subqueries_without_rebinding_ordinary_input_casts() {
    let mut statement = crate::compile("SELECT (SELECT 4), '(4)'::text")
        .unwrap()
        .remove(0);
    crate::catalog::stored_ast::visit_stored_statement_expressions(&mut statement, &mut |node| {
        if *node == Expr::Literal(Value::Int(4)) {
            *node = Expr::TypedLiteral {
                composite_source: None,
                value: value(),
                ty: pair_type().catalog_name(),
            };
        }
        Ok(())
    })
    .unwrap();
    let added = AttributeChange::Add("b".into());
    let change = change(&added);
    let mut plan = UnifiedPlan::lower(statement.clone());
    assert!(change.statement(&mut statement).unwrap());
    assert!(change.plan(&mut plan).unwrap());
    assert_eq!(
        serde_json::to_value(&plan).unwrap(),
        serde_json::to_value(UnifiedPlan::lower(statement)).unwrap()
    );
    let mut seen = 0;
    plan.rewrite_scalar_expressions(&mut |node| {
        if let ScalarExpr::TypedLiteral { value, ty, .. } = node {
            seen += 1;
            assert_eq!(*ty, pair_type().catalog_name());
            assert_eq!(
                *value,
                Value::Record(vec![("a".into(), Value::Int(4)), ("b".into(), Value::Null)].into())
            );
        }
    });
    assert_eq!(seen, 1);
}

#[test]
fn removing_a_field_preserves_its_original_datum_and_descriptor_through_serialization() {
    let mut expression = Expr::TypedLiteral {
        composite_source: None,
        value: value(),
        ty: pair_type().catalog_name(),
    };
    assert!(change(&AttributeChange::Drop("a".into()))
        .expression(&mut expression)
        .unwrap());
    let encoded = serde_json::to_string(&expression).unwrap();
    let restored: Expr = serde_json::from_str(&encoded).unwrap();
    assert_eq!(expression, restored);
    let Expr::TypedLiteral {
        value: projected,
        composite_source: Some(source),
        ..
    } = restored
    else {
        panic!("retained composite datum")
    };
    assert_eq!(projected, Value::Record(Vec::new().into()));
    assert_eq!(source.value, value());
    assert_eq!(source.descriptors.len(), 1);
    assert_eq!(source.descriptors[0].attributes[0].number, 1);
    assert_eq!(source.descriptors[0].attributes[0].ty, ColumnType::Integer);
}

#[test]
fn array_constants_keep_bounds_nulls_and_existing_fields() {
    let added = AttributeChange::Add("b".into());
    let change = change(&added);
    let mut expression = Expr::TypedLiteral {
        composite_source: None,
        value: Value::Array(
            ArrayValue::with_lower_bounds(vec![value(), Value::Null], vec![-3]).unwrap(),
        ),
        ty: ColumnType::Array(Box::new(pair_type())).catalog_name(),
    };
    assert!(change.expression(&mut expression).unwrap());
    let Expr::TypedLiteral {
        value: Value::Array(array),
        ..
    } = expression
    else {
        panic!("array datum")
    };
    assert_eq!(array.lower_bounds(), [-3]);
    assert_eq!(array.dimensions(), [2]);
    assert_eq!(
        array.elements(),
        [
            Value::Record(vec![("a".into(), Value::Int(4)), ("b".into(), Value::Null)].into()),
            Value::Null
        ]
    );
}

#[test]
fn missing_durable_type_identity_is_rejected_without_discarding_the_value() {
    let mut expression = Expr::TypedLiteral {
        composite_source: None,
        value: value(),
        ty: "composite#99999".into(),
    };
    let before = expression.clone();
    assert!(change(&AttributeChange::Add("b".into()))
        .expression(&mut expression)
        .unwrap_err()
        .to_string()
        .contains("disappeared"));
    assert_eq!(expression, before);
}