use super::*;
fn ids(list: &PostingList) -> Vec<DocId> {
list.entries().iter().map(|e| e.doc_id).collect()
}
#[test]
fn build_scan_equals_and_ranges() {
let index = ColumnValueIndex::build(
"qty",
vec![
(1, Value::Int(10)),
(2, Value::Int(20)),
(3, Value::Int(20)),
(4, Value::Null),
(5, Value::Int(30)),
]
.into_iter(),
);
assert_eq!(
ids(&index.scan(&Predicate::Equals(Value::Int(20))).unwrap()),
vec![2, 3]
);
assert_eq!(
ids(&index.scan(&Predicate::GreaterThan(Value::Int(10))).unwrap()),
vec![2, 3, 5]
);
assert_eq!(
ids(&index
.scan(&Predicate::Between {
low: Value::Int(10),
high: Value::Int(20),
})
.unwrap()),
vec![1, 2, 3]
);
assert_eq!(ids(&index.scan(&Predicate::IsNull).unwrap()), vec![4]);
assert_eq!(
ids(&index.scan(&Predicate::IsNotNull).unwrap()),
vec![1, 2, 3, 5]
);
assert!(index.scan(&Predicate::NotEquals(Value::Int(10))).is_none());
}
#[test]
fn incremental_insert_remove_tracks_nulls() {
let mut index = ColumnValueIndex::build("qty", std::iter::empty());
index.insert(7, &Value::Int(1));
index.insert(8, &Value::Null);
assert_eq!(
ids(&index.scan(&Predicate::Equals(Value::Int(1))).unwrap()),
vec![7]
);
assert_eq!(ids(&index.scan(&Predicate::IsNull).unwrap()), vec![8]);
index.remove(7, &Value::Int(1));
index.remove(8, &Value::Null);
assert!(ids(&index.scan(&Predicate::Equals(Value::Int(1))).unwrap()).is_empty());
assert!(ids(&index.scan(&Predicate::IsNull).unwrap()).is_empty());
}
#[test]
fn temporal_and_nan_guards_refuse_acceleration() {
let temporal = uqa_core::TemporalValue::parse_date("2024-01-01").unwrap();
let index = ColumnValueIndex::build(
"ts",
vec![(1, Value::Temporal(temporal.clone()))].into_iter(),
);
assert!(index
.scan(&Predicate::Equals(Value::Str("2024-01-01".into())))
.is_none());
let numeric = ColumnValueIndex::build("f", vec![(1, Value::Float(1.0))].into_iter());
assert!(numeric
.scan(&Predicate::Equals(Value::Float(f64::NAN)))
.is_none());
assert!(numeric
.scan(&Predicate::Equals(Value::Temporal(temporal)))
.is_none());
}
#[test]
fn selected_reads_observe_before_empty_or_cached_results_and_propagate_failure() {
let index = ColumnValueIndex::build("v", [(1, Value::Int(10))].into_iter());
for target in [10, 99] {
let mut observed = false;
let result = index
.scan_observing(&Predicate::Equals(Value::Int(target)), || {
observed = true;
Ok(())
})
.unwrap()
.unwrap();
assert!(observed);
assert_eq!(result.len(), usize::from(target == 10));
}
let declined = index
.scan_observing(&Predicate::NotEquals(Value::Int(10)), || {
panic!("a declined predicate is not an observed index read")
})
.unwrap();
assert!(declined.is_none());
let error = index
.scan_observing(&Predicate::Equals(Value::Int(10)), || {
Err(uqa_sql::SQLError::Routine {
sqlstate: "40001".into(),
message: "observation failed".into(),
})
})
.unwrap_err();
assert_eq!(error.sqlstate(), Some("40001"));
assert_eq!(
ids(&index.scan(&Predicate::Equals(Value::Int(10))).unwrap()),
vec![1]
);
}
#[test]
fn legacy_vector_index_probes_preserve_operator_failures_without_rejecting_single_keys() {
let invalid = Value::LegacyVector(
uqa_core::LegacyVectorValue::try_from_array(
uqa_core::LegacyVectorKind::Oid,
uqa_core::ArrayValue::with_lower_bounds(vec![], vec![]).unwrap(),
)
.unwrap(),
);
let valid = uqa_sql::expr::cast_value(&Value::Str("2".into()), "oidvector").unwrap();
let empty = ColumnValueIndex::build("v", std::iter::empty());
assert!(empty
.scan_observing(&Predicate::Equals(invalid.clone()), || Ok(()))
.unwrap()
.unwrap()
.is_empty());
for stored in [invalid.clone(), valid.clone()] {
let index = ColumnValueIndex::build("v", [(1, stored)].into_iter());
assert_eq!(
index
.scan_observing(&Predicate::Equals(invalid.clone()), || Ok(()))
.unwrap_err()
.sqlstate(),
Some("42804")
);
assert_eq!(
ids(&index
.scan_observing(&Predicate::IsNotNull, || Ok(()))
.unwrap()
.unwrap()),
vec![1]
);
}
let index = ColumnValueIndex::build("v", [(1, invalid)].into_iter());
assert_eq!(
index
.scan_observing(&Predicate::Equals(valid), || Ok(()))
.unwrap_err()
.sqlstate(),
Some("42804")
);
}