use super::*;
use uqa_core::{ArrayValue, TemporalValue};
fn array(values: Vec<Value>) -> Value {
Value::Array(ArrayValue::try_new(values).unwrap())
}
fn shifted(values: Vec<Value>, bounds: Vec<i32>) -> Value {
Value::Array(ArrayValue::with_lower_bounds(values, bounds).unwrap())
}
#[test]
fn array_ranges_preserve_elements_dimensions_lower_bounds_and_fractional_cuts() {
let values = vec![
Value::Null,
array(vec![]),
array(vec![Value::Int(-1)]),
array(vec![Value::Int(0)]),
array(vec![Value::Int(1)]),
array(vec![Value::Int(1), Value::Int(-9)]),
array(vec![Value::Int(1), Value::Int(0)]),
array(vec![Value::Int(1), Value::Int(1)]),
array(vec![Value::Int(1), Value::Null]),
array(vec![Value::Null]),
array(vec![Value::List(vec![])]),
array(vec![Value::List(vec![Value::Int(1), Value::Int(0)])]),
shifted(vec![Value::Int(1), Value::Int(0)], vec![-2]),
shifted(vec![Value::Int(1), Value::Int(0)], vec![2]),
array(vec![Value::Int(i64::MIN)]),
array(vec![Value::Int(i64::MAX)]),
];
let mut targets = values.clone();
targets.extend([
array(vec![Value::Float(0.5), Value::Null]),
array(vec![Value::Int(1), Value::Float(0.5)]),
array(vec![decimal("1.00"), Value::Int(0)]),
array(vec![Value::Float(f64::INFINITY)]),
array(vec![Value::Float(f64::NEG_INFINITY)]),
array(vec![decimal("9223372036854775808")]),
array(vec![Value::Str("one".into())]),
Value::Void,
Value::Bool(true),
Value::Str("{}".into()),
Value::JsonB("[]".into()),
Value::List(vec![]),
Value::Row(vec![]),
Value::Record(vec![]),
Value::Map(std::collections::BTreeMap::new()),
]);
verify(
IndexDomain::Array(ScalarIndexDomain::Integer),
&values,
&targets,
);
}
#[test]
fn float_array_cuts_canonicalize_signed_zero_and_preserve_nested_nan_order() {
let mut values = vec![Value::Null, array(vec![]), array(vec![Value::Null])];
for value in [
f64::NEG_INFINITY,
-f64::from_bits(1),
-0.0,
0.0,
f64::from_bits(1),
0.1,
1.0,
1.000_000_000_000_000_2,
f64::INFINITY,
f64::NAN,
] {
values.push(array(vec![Value::Float(value)]));
values.push(array(vec![Value::Float(value), Value::Float(1.0)]));
}
let mut targets = values.clone();
targets.extend([
array(vec![Value::Int(0)]),
array(vec![decimal("0"), Value::Int(1)]),
array(vec![decimal("0.100000000000000001"), Value::Null]),
array(vec![decimal("1.0000000000000001"), Value::Int(-100)]),
array(vec![Value::Int(i64::MAX)]),
array(vec![decimal("NaN")]),
]);
verify(
IndexDomain::Array(ScalarIndexDomain::Float),
&values,
&targets,
);
}
#[test]
fn legacy_vector_domains_preserve_kind_order_and_atomic_array_elements() {
use uqa_core::{LegacyVectorKind, LegacyVectorValue};
for kind in [LegacyVectorKind::SmallInteger, LegacyVectorKind::Oid] {
let mut values = vec![Value::Null];
values.extend(
[vec![], vec![2], vec![1, 9], vec![1, 2, 0], vec![1, 2]]
.into_iter()
.map(|values| {
Value::LegacyVector(
LegacyVectorValue::try_new(
kind,
values.into_iter().map(Value::Int).collect(),
)
.unwrap(),
)
}),
);
let mut targets = values.clone();
targets.extend([
array(vec![Value::Int(1)]),
Value::List(vec![Value::Int(1)]),
Value::Int(1),
Value::Row(vec![]),
Value::LegacyVector(
LegacyVectorValue::try_new(
if kind == LegacyVectorKind::SmallInteger {
LegacyVectorKind::Oid
} else {
LegacyVectorKind::SmallInteger
},
vec![Value::Int(1), Value::Int(2)],
)
.unwrap(),
),
]);
let domain = ScalarIndexDomain::LegacyVector(kind);
verify(IndexDomain::Scalar(domain), &values, &targets);
let arrays: Vec<_> = values
.iter()
.map(|value| array(vec![value.clone()]))
.collect();
verify(IndexDomain::Array(domain), &arrays, &arrays);
}
}
#[test]
fn array_leaf_domains_keep_native_comparison_without_scalar_text_coercion() {
for (domain, leaves, foreign) in [
(
ScalarIndexDomain::Text,
vec![
Value::Str(String::new()),
Value::Str("a\0".into()),
Value::Str("aa".into()),
],
Value::FixedChar("a".into()),
),
(
ScalarIndexDomain::FixedChar,
vec![
Value::FixedChar("a".into()),
Value::FixedChar("a ".into()),
Value::FixedChar("b".into()),
],
Value::Str("a".into()),
),
(
ScalarIndexDomain::Bytes,
vec![
Value::Bytes(vec![]),
Value::Bytes(vec![0]),
Value::Bytes(vec![0, 255]),
],
Value::Str(String::new()),
),
(
ScalarIndexDomain::JsonText,
vec![Value::Json("[]".into()), Value::Json("{}".into())],
Value::JsonB("[]".into()),
),
(
ScalarIndexDomain::JsonBinary,
vec![
Value::JsonB("[]".into()),
Value::JsonB("{\"a\":1.0}".into()),
Value::JsonB("{\"a\":1}".into()),
],
Value::Json("[]".into()),
),
(
ScalarIndexDomain::Decimal,
vec![
decimal("-1"),
decimal("0"),
decimal("1.00"),
decimal("1.01"),
],
Value::Float(1.0),
),
(
ScalarIndexDomain::Temporal(TemporalIndexDomain::Date),
vec![
Value::Temporal(TemporalValue::Date { days: 0 }),
Value::Temporal(TemporalValue::Date { days: 1 }),
],
Value::Str("1970-01-01".into()),
),
] {
let mut values = vec![Value::Null, array(vec![]), array(vec![Value::Null])];
for leaf in leaves {
values.extend([
array(vec![leaf.clone()]),
array(vec![leaf.clone(), Value::Null]),
array(vec![leaf.clone(), leaf]),
]);
}
let mut targets = values.clone();
targets.push(array(vec![foreign]));
verify(IndexDomain::Array(domain), &values, &targets);
}
}
#[test]
fn vector_and_tensor_keys_preserve_list_boundaries_and_native_leaf_comparison() {
let vector =
|values: &[f64]| Value::List(values.iter().map(|value| Value::Float(*value)).collect());
let vectors = vec![
Value::Null,
vector(&[]),
vector(&[-1.0]),
vector(&[-0.0]),
vector(&[0.0, 1.0]),
vector(&[1.0]),
Value::List(vec![Value::Null]),
];
let mut targets = vectors.clone();
targets.extend([
Value::List(vec![Value::Int(0), Value::Int(1)]),
Value::List(vec![decimal("0.000000000000000001")]),
array(vec![]),
Value::Row(vec![]),
]);
verify(
IndexDomain::List(ScalarIndexDomain::Float),
&vectors,
&targets,
);
let tensors = vec![
Value::Null,
Value::List(vec![]),
Value::List(vec![vector(&[])]),
Value::List(vec![vector(&[0.0, 1.0])]),
Value::List(vec![vector(&[0.0]), vector(&[1.0])]),
Value::List(vec![vector(&[1.0])]),
Value::List(vec![Value::Null]),
];
let mut targets = tensors.clone();
targets.extend(vectors);
targets.extend([
Value::List(vec![Value::List(vec![Value::Int(0), Value::Int(1)])]),
Value::List(vec![Value::List(vec![decimal("0.5"), Value::Null])]),
Value::List(vec![Value::Row(vec![])]),
array(vec![]),
Value::Row(vec![]),
]);
verify(IndexDomain::Tensor, &tensors, &targets);
}
#[test]
fn declared_container_domains_and_resource_errors_keep_original_control() {
for (ty, domain) in [
(
ColumnType::Int2Vector,
IndexDomain::Scalar(ScalarIndexDomain::LegacyVector(
uqa_core::LegacyVectorKind::SmallInteger,
)),
),
(
ColumnType::OidVector,
IndexDomain::Scalar(ScalarIndexDomain::LegacyVector(
uqa_core::LegacyVectorKind::Oid,
)),
),
(
ColumnType::Array(Box::new(ColumnType::Array(Box::new(ColumnType::Integer)))),
IndexDomain::Array(ScalarIndexDomain::Integer),
),
(
ColumnType::Vector(2),
IndexDomain::List(ScalarIndexDomain::Float),
),
(ColumnType::Tensor(2), IndexDomain::Tensor),
] {
assert_eq!(IndexDomain::from_column_type(&ty), Some(domain));
let alias = ColumnType::Domain {
schema: "public".into(),
name: "container_value".into(),
oid: 42_002,
base: Box::new(ty),
};
assert_eq!(IndexDomain::from_column_type(&alias), Some(domain));
}
let value = array(vec![Value::Str("a".repeat(100))]);
let domain = IndexDomain::Array(ScalarIndexDomain::Text);
let control = StorageReadControl::with_limit(64);
let error = domain.encode(&value, &control).unwrap_err();
assert_eq!(error.sqlstate(), Some("53200"));
assert_eq!(control.memory().used(), 0);
let mut visited = false;
let error = domain
.visit_predicate(&Predicate::Equals(value.clone()), &control, &mut |_| {
visited = true;
Ok(())
})
.unwrap_err();
assert!(!visited);
assert_eq!(error.sqlstate(), Some("53200"));
assert_eq!(control.memory().used(), 0);
control.cancellation().cancel();
let error = domain.encode(&value, &control).unwrap_err();
assert_eq!(error.sqlstate(), Some("57014"));
assert_eq!(control.memory().used(), 0);
}
proptest! {
#[test]
fn mixed_array_predicates_match_actual_index_selection(
values in prop::collection::vec(any::<i64>(), 0..5),
targets in prop::collection::vec(-1.0e19_f64..1.0e19, 0..5),
) {
let value = array(values.into_iter().map(Value::Int).collect());
let target = array(targets.into_iter().map(Value::Float).collect());
let index = ColumnValueIndex::build("key", std::iter::once((1, value.clone())));
for predicate in [Predicate::Equals(target.clone()), Predicate::GreaterThan(target.clone()), Predicate::GreaterThanOrEqual(target.clone()), Predicate::LessThan(target.clone()), Predicate::LessThanOrEqual(target)] {
prop_assert_eq!(observed(IndexDomain::Array(ScalarIndexDomain::Integer), &predicate, &value), !index.scan(&predicate).unwrap().is_empty());
}
}
}