use super::*;
use crate::background::CheckpointWorkProbe;
use hawdb_qos::{LocalQosPolicy, LocalQosScheduler};
use std::sync::atomic::Ordering as AtomicOrdering;
use std::sync::Arc;
fn scheduler() -> LocalQosScheduler {
LocalQosScheduler::new(LocalQosPolicy {
max_background_operations: Some(1),
max_total_background_operations: Some(1),
..LocalQosPolicy::default()
})
}
#[test]
fn checkpoint_units_predicate_evaluation_matches_ordinary_value_and_missing_property_semantics() {
let wide = format!("{}\0界🦀", "a".repeat(64 * 1024 + 1));
let values = vec![
Value::Null,
Value::Bool(false),
Value::Bool(true),
Value::Int(0),
Value::Int(i64::MAX),
Value::Float(0.0),
Value::Float(-0.0),
Value::Float(f64::INFINITY),
Value::Float(f64::from_bits(0x7ff8_0000_0000_0001)),
Value::Float(f64::from_bits(0x7ff8_0000_0000_0002)),
Value::String(String::new()),
Value::String(wide.clone()),
Value::String(format!("{wide}z")),
Value::Binary(vec![0xff; 64 * 1024 + 1]),
Value::Uuid(hawdb_core::Uuid::from_u128(17)),
Value::Vector(hawdb_core::Vector::new(&[1.0, -0.0]).unwrap()),
Value::Vector(hawdb_core::Vector::new(&[1.0, 0.0]).unwrap()),
Value::List(vec![Value::Int(0), Value::String(wide.clone())]),
Value::Map(BTreeMap::from([(
wide.clone(),
Value::List(vec![Value::Binary(vec![0x9f; 64 * 1024 + 1])]),
)])),
];
let local = scheduler();
for actual in &values {
let properties = BTreeMap::from([
("a-before".into(), Value::Int(1)),
(wide.clone(), actual.clone()),
("z-after".into(), Value::Bool(true)),
]);
for expected in &values {
for name in [&wide, &format!("{wide}missing")] {
for predicate in [
ProjectedRelationshipPredicate::Eq {
property: name.clone(),
value: expected.clone(),
},
ProjectedRelationshipPredicate::Gte {
property: name.clone(),
value: expected.clone(),
},
] {
let probe = Arc::new(CheckpointWorkProbe::default());
assert_eq!(
matches(&predicate, &properties, &probe.context(local.clone())).unwrap(),
predicate.matches(&properties),
"actual type={:?}, expected type={:?}",
actual.logical_type(),
expected.logical_type()
);
assert_eq!(probe.peak_units.load(AtomicOrdering::SeqCst), 1);
probe.assert_released(&local);
}
}
}
}
}
#[test]
fn checkpoint_units_predicate_evaluation_splits_wide_comparisons_before_completion() {
let local = scheduler();
for value in [
Value::Binary(vec![0x9f; 32 * 64 * 1024 + 1]),
Value::Vector(hawdb_core::Vector::new(&vec![1.0; 32 * 16 * 1024 + 1]).unwrap()),
Value::String(format!("{}界", "a".repeat(32 * 64 * 1024 + 1))),
] {
let bytes = match &value {
Value::Binary(bytes) => bytes.len(),
Value::Vector(value) => value.encoded().len(),
Value::String(text) => text.len(),
_ => unreachable!(),
};
let properties = BTreeMap::from([("payload".into(), value.clone())]);
let predicate = ProjectedRelationshipPredicate::Eq {
property: "payload".into(),
value,
};
let probe = Arc::new(CheckpointWorkProbe::default());
assert!(matches(&predicate, &properties, &probe.context(local.clone())).unwrap());
let minimum = bytes.div_ceil(64 * 1024);
assert!(
probe.completed.load(AtomicOrdering::SeqCst) >= minimum,
"wide comparison must yield before processing another 64 KiB"
);
probe.assert_released(&local);
let cancelled = Arc::new(CheckpointWorkProbe::default());
cancelled
.cancel_after
.store(minimum / 2, AtomicOrdering::SeqCst);
assert_eq!(
matches(&predicate, &properties, &cancelled.context(local.clone()))
.unwrap_err()
.to_string(),
"storage error: checkpoint build stopped: cancelled"
);
cancelled.assert_released(&local);
let retry = Arc::new(CheckpointWorkProbe::default());
assert!(matches(&predicate, &properties, &retry.context(local.clone())).unwrap());
retry.assert_released(&local);
}
}
#[test]
fn checkpoint_units_predicate_evaluation_cancels_each_actual_unit_and_retries_complete_input() {
let name = format!("{}界", "a".repeat(2 * 64 * 1024 + 1));
let value = Value::Map(BTreeMap::from([(
name.clone(),
Value::List(vec![
Value::String("\0界🦀".repeat(17000)),
Value::Binary(vec![0x9f; 3 * 64 * 1024 + 1]),
Value::Float(-0.0),
Value::Vector(hawdb_core::Vector::new(&vec![1.0; 3 * 16 * 1024 + 1]).unwrap()),
]),
)]));
let properties = BTreeMap::from([(name.clone(), value.clone())]);
let predicates = [
ProjectedRelationshipPredicate::And(Vec::new()),
ProjectedRelationshipPredicate::And(vec![
ProjectedRelationshipPredicate::Eq {
property: name.clone(),
value,
},
ProjectedRelationshipPredicate::And(vec![ProjectedRelationshipPredicate::Gte {
property: name.clone(),
value: Value::Int(1),
}]),
]),
ProjectedRelationshipPredicate::Gte {
property: format!("{name}missing"),
value: Value::String(name),
},
];
let local = scheduler();
for predicate in predicates {
let expected = predicate.matches(&properties);
let probe = Arc::new(CheckpointWorkProbe::default());
assert_eq!(
matches(&predicate, &properties, &probe.context(local.clone())).unwrap(),
expected
);
let total = probe.completed.load(AtomicOrdering::SeqCst);
probe.assert_released(&local);
for cut in 1..=total {
let cancelled = Arc::new(CheckpointWorkProbe::default());
cancelled.cancel_after.store(cut, AtomicOrdering::SeqCst);
assert_eq!(
matches(&predicate, &properties, &cancelled.context(local.clone()))
.unwrap_err()
.to_string(),
"storage error: checkpoint build stopped: cancelled"
);
assert_eq!(cancelled.completed.load(AtomicOrdering::SeqCst), cut);
cancelled.assert_released(&local);
}
let retry = Arc::new(CheckpointWorkProbe::default());
assert_eq!(
matches(&predicate, &properties, &retry.context(local.clone())).unwrap(),
expected
);
retry.assert_released(&local);
}
}