uqa-execution 0.5.3

Volcano physical operators with row-batch pipelines
//
// Unified Query Algebra
//
// Copyright (c) 2023-2026 Cognica, Inc.
//

use super::*;
use uqa_core::{ArrayValue, EnumLabelKey, EnumValue};

struct EnumIdentityCatalog;

impl uqa_sql::expr::enums::EnumLabelCatalog for EnumIdentityCatalog {
    fn enum_type_labels(
        &self,
        oid: u32,
    ) -> Result<Option<std::sync::Arc<uqa_sql::expr::enums::EnumTypeLabels>>, uqa_sql::SQLError>
    {
        Ok((oid == 16_384).then(|| {
            std::sync::Arc::new(uqa_sql::expr::enums::EnumTypeLabels {
                type_oid: oid,
                labels: Vec::new(),
            })
        }))
    }

    fn enum_label_uncommitted(&self, _: u32) -> bool {
        false
    }

    fn enum_type_name(&self, _: u32) -> Result<Option<String>, uqa_sql::SQLError> {
        unreachable!("hashing must not format an enum label")
    }

    fn has_enum_types(&self) -> bool {
        true
    }
}

#[test]
fn sql_enum_hash_matches_raw_and_admitted_identities_without_label_output() {
    let build = std::collections::hash_map::RandomState::new();
    let physical = |oid: u32| {
        Value::Datum(uqa_core::DatumValue::new(
            16_384,
            0,
            oid.to_le_bytes().to_vec(),
        ))
    };
    let native = Value::Enum(
        EnumValue::new(16_384, EnumLabelKey::initial(1).unwrap().remove(0)).with_label_oid(Some(5)),
    );
    let hash = |value: &Value| {
        hash_canonical_row_with_enum_catalog(
            &build,
            std::iter::once(Some(value)),
            Some(&EnumIdentityCatalog),
        )
        .unwrap()
    };
    let equal = |left: &Value, right: &Value| {
        uqa_sql::expr::equal_typed_values_with_enum_catalog(
            left,
            right,
            &uqa_core::memory::ProductionControl::uncontrolled(),
            Some(&EnumIdentityCatalog),
        )
        .unwrap()
    };
    for (left, right) in [(physical(5), native), (physical(1), physical(1))] {
        assert!(equal(&left, &right));
        assert_eq!(hash(&left), hash(&right));
        let left = Value::Array(ArrayValue::try_new(vec![left]).unwrap());
        let right = Value::Array(ArrayValue::try_new(vec![right]).unwrap());
        assert!(equal(&left, &right));
        assert_eq!(hash(&left), hash(&right));
        let left = Value::Record(vec![("value".into(), left)].into());
        let right = Value::Record(vec![("value".into(), right)].into());
        assert!(equal(&left, &right));
        assert_eq!(hash(&left), hash(&right));
    }
    assert!(!equal(&physical(1), &physical(5)));
}

#[test]
fn controlled_keys_keep_all_canonical_domains_and_only_retain_the_output_buffer() {
    let control = StorageReadControl::with_limit(8 * 1024 * 1024);
    let values = vec![
        Value::Null,
        Value::Void,
        Value::Bool(true),
        Value::Int(i64::MIN),
        Value::Int(i64::MAX),
        Value::Float(-0.0),
        Value::Float(f64::MAX),
        Value::Float(f64::from_bits(1)),
        Value::Float(f64::INFINITY),
        Value::Float(f64::NEG_INFINITY),
        Value::Float(f64::NAN),
        Value::Decimal(DecimalValue::parse("-12.34000").unwrap()),
        Value::Str("a\0é".into()),
        Value::FixedChar("padded   ".into()),
        Value::Bytes(vec![0, 255]),
        Value::Json(" {\"x\":1} ".into()),
        Value::JsonB("{\"x\":1.00,\"a\":[true,null]}".into()),
        Value::Temporal(TemporalValue::TimeTz {
            micros: 3_600_000_000,
            offset_minutes: 60,
        }),
        Value::Array(
            ArrayValue::with_lower_bounds(vec![Value::Int(1), Value::Null], vec![-2]).unwrap(),
        ),
        Value::List(vec![Value::Bool(true)]),
        Value::Row(vec![Value::Int(2)].into()),
        Value::Record(vec![("ignored name".into(), Value::Int(3))].into()),
        Value::Map(std::collections::BTreeMap::from([(
            "name".into(),
            Value::Int(4),
        )])),
    ];
    for value in &values {
        let key = canonical_row_key_budgeted(std::iter::once(Some(value)), &control).unwrap();
        assert_eq!(
            &*key,
            canonical_row_key(std::slice::from_ref(value)).unwrap()
        );
        assert_eq!(control.memory().used(), key.capacity());
        drop(key);
        assert_eq!(control.memory().used(), 0);
    }
    let null = canonical_row_key_budgeted(std::iter::once(None), &control).unwrap();
    assert_eq!(&*null, canonical_row_key(&[Value::Null]).unwrap());
}

#[test]
fn controlled_numeric_domains_keep_the_persisted_integer_bytes_and_signed_zero() {
    let control = StorageReadControl::with_limit(1024 * 1024);
    let expected = [0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, b'1'];
    for value in [
        Value::Bool(true),
        Value::Int(1),
        Value::Float(1.0),
        Value::Decimal(DecimalValue::parse("1.000").unwrap()),
    ] {
        let key = canonical_row_key_budgeted(std::iter::once(Some(&value)), &control).unwrap();
        assert_eq!(&*key, expected);
    }
    assert_eq!(
        canonical_row_key(&[Value::Float(-0.0)]).unwrap(),
        canonical_row_key(&[Value::Int(0)]).unwrap()
    );
}

#[test]
fn normalization_failures_keep_memory_and_cancellation_diagnostics() {
    for (label, value) in [
        ("text", Value::Str("x".repeat(8192))),
        ("jsonb", Value::JsonB(format!("[\"{}\"]", "x".repeat(8192)))),
        (
            "decimal",
            Value::Decimal(DecimalValue::parse("1e131071").unwrap()),
        ),
        ("float", Value::Float(f64::from_bits(1))),
    ] {
        let control = StorageReadControl::with_limit(256);
        let error =
            canonical_row_key_budgeted(std::iter::once(Some(&value)), &control).unwrap_err();
        assert!(
            matches!(error, ExecError::SQL(ref error) if error.sqlstate() == Some("53200")),
            "{label}: {error}"
        );
        assert_eq!(control.memory().used(), 0);
    }
    let control = StorageReadControl::with_limit(256);
    control.cancellation().cancel();
    assert!(matches!(
        canonical_row_key_budgeted(std::iter::once(Some(&Value::Int(1))), &control),
        Err(ExecError::SQL(uqa_sql::SQLError::Cancelled(_)))
    ));
    assert_eq!(control.memory().used(), 0);
}

#[test]
fn canonical_numeric_keys_match_value_equality_at_precision_boundaries() {
    let decimal = |text| Value::Decimal(DecimalValue::parse(text).unwrap());
    let values = [
        Value::Int(9_223_372_036_854_774_784),
        Value::Float(9_223_372_036_854_774_784_i64 as f64),
        decimal("9223372036854774784"),
        decimal("9223372036854775000"),
        Value::Float(0.1),
        decimal("0.1"),
        decimal("0.1000000000000000055511151231257827021181583404541015625"),
        Value::Float(f64::from_bits(1)),
        Value::Int(0),
        Value::Float(-0.0),
        decimal("0.000"),
        Value::Float(f64::NAN),
        decimal("NaN"),
        Value::Float(f64::INFINITY),
        decimal("Infinity"),
        Value::Float(f64::NEG_INFINITY),
        decimal("-Infinity"),
    ];
    let control = StorageReadControl::with_limit(64 * 1024);
    let hash_state = std::collections::hash_map::RandomState::new();
    for left in &values {
        let left_key = canonical_row_key(std::slice::from_ref(left)).unwrap();
        let controlled = canonical_row_key_budgeted(std::iter::once(Some(left)), &control).unwrap();
        assert_eq!(&*controlled, &left_key);
        drop(controlled);
        assert_eq!(control.memory().used(), 0);
        for right in &values {
            let right_key = canonical_row_key(std::slice::from_ref(right)).unwrap();
            assert_eq!(left_key == right_key, left == right, "{left:?}, {right:?}");
            if left == right {
                assert_eq!(
                    hash_canonical_row(&hash_state, std::iter::once(Some(left))).unwrap(),
                    hash_canonical_row(&hash_state, std::iter::once(Some(right))).unwrap()
                );
            }
            let left_row = Value::Row(vec![left.clone(), Value::Null].into());
            let right_row = Value::Row(vec![right.clone(), Value::Null].into());
            assert_eq!(
                canonical_row_key(std::slice::from_ref(&left_row)).unwrap()
                    == canonical_row_key(std::slice::from_ref(&right_row)).unwrap(),
                left_row == right_row
            );
        }
    }
}

#[test]
fn nested_values_use_a_charged_traversal_stack_without_cloning_their_payload() {
    let control = StorageReadControl::with_limit(1024 * 1024);
    let mut value = Value::Str("original payload".into());
    for _ in 0..256 {
        value = Value::List(vec![value]);
    }
    let key = canonical_row_key_budgeted(std::iter::once(Some(&value)), &control).unwrap();
    assert_eq!(
        &*key,
        canonical_row_key(std::slice::from_ref(&value)).unwrap()
    );
    assert_eq!(control.memory().used(), key.capacity());
}

#[test]
fn legacy_vector_hash_validation_does_not_reject_opaque_reservation_addresses() {
    use uqa_core::{ArrayValue, LegacyVectorKind, LegacyVectorValue};
    let invalid = Value::LegacyVector(
        LegacyVectorValue::try_from_array(
            LegacyVectorKind::Oid,
            ArrayValue::try_new(Vec::new()).unwrap(),
        )
        .unwrap(),
    );
    for value in [
        invalid.clone(),
        Value::Array(ArrayValue::try_new(vec![invalid]).unwrap()),
    ] {
        crate::canonical_row_key(std::slice::from_ref(&value)).unwrap();
        let hasher = std::collections::hash_map::RandomState::new();
        let ExecError::SQL(error) =
            crate::hash_canonical_row(&hasher, std::iter::once(Some(&value))).unwrap_err()
        else {
            panic!("expected SQL hash failure")
        };
        assert_eq!(error.sqlstate(), Some("42804"));
        assert_eq!(error.to_string(), "array is not a valid oidvector");
    }
}

#[test]
fn enum_keys_are_injective_and_congruent_with_value_equality() {
    let label = |type_oid, key: &[u8]| {
        Value::Enum(EnumValue::new(
            type_oid,
            EnumLabelKey::from_bytes(key.to_vec()).unwrap(),
        ))
    };
    let values = [
        label(7, &[64]),
        label(7, &[64, 1]),
        label(7, &[128]),
        label(8, &[64]),
        label(0x0100_0000, &[64]),
        Value::Bytes(vec![64]),
        Value::Array(ArrayValue::try_new(vec![label(7, &[64])]).unwrap()),
        Value::Record(vec![("field".into(), label(7, &[64]))].into()),
    ];
    for left in &values {
        for right in &values {
            let left_key = canonical_row_key(std::slice::from_ref(left)).unwrap();
            let right_key = canonical_row_key(std::slice::from_ref(right)).unwrap();
            assert_eq!(left_key == right_key, left == right, "{left:?} / {right:?}");
        }
    }
}