use vortex_error::VortexExpect;
use vortex_error::vortex_panic;
use crate::builders::LazyBitBufferBuilder;
use crate::dtype::Nullability;
use crate::dtype::Nullability::NonNullable;
use crate::dtype::Nullability::Nullable;
use crate::validity::Validity;
pub(crate) struct ValidityBuilder {
runs: Vec<(Validity, usize)>,
runs_len: usize,
pending: LazyBitBufferBuilder,
}
impl ValidityBuilder {
pub fn new(capacity: usize) -> Self {
Self {
runs: Vec::new(),
runs_len: 0,
pending: LazyBitBufferBuilder::new(capacity),
}
}
pub fn len(&self) -> usize {
self.runs_len + self.pending.len()
}
pub fn append_non_null(&mut self) {
self.pending.append_non_null()
}
pub fn append_n_non_nulls(&mut self, n: usize) {
self.pending.append_n_non_nulls(n)
}
pub fn append_n_nulls(&mut self, n: usize) {
self.pending.append_n_nulls(n)
}
pub fn append_validity(&mut self, validity: Validity, len: usize) {
if len == 0 {
return;
}
self.flush_pending();
self.runs_len += len;
self.runs.push((validity, len));
}
pub fn reserve_exact(&mut self, additional: usize) {
self.pending.reserve_exact(additional)
}
pub fn finish_with_nullability(&mut self, nullability: Nullability) -> Validity {
if self.runs.is_empty() {
return self.pending.finish_with_nullability(nullability);
}
self.flush_pending();
self.runs_len = 0;
let runs = std::mem::take(&mut self.runs);
if runs
.iter()
.all(|(validity, _)| validity.definitely_no_nulls())
{
return nullability.into();
}
let validity = Validity::concat(runs).vortex_expect("runs is not empty");
if nullability == NonNullable {
vortex_panic!("cannot finish a non-nullable builder holding {validity:?} validity");
}
validity
}
fn flush_pending(&mut self) {
let len = self.pending.len();
if len == 0 {
return;
}
let validity = self.pending.finish_with_nullability(Nullable);
self.runs_len += len;
self.runs.push((validity, len));
}
}
#[cfg(test)]
mod tests {
use vortex_error::VortexResult;
use vortex_mask::Mask;
use super::ValidityBuilder;
use crate::VortexSessionExecute;
use crate::array_session;
use crate::arrays::Chunked;
use crate::dtype::Nullability::NonNullable;
use crate::dtype::Nullability::Nullable;
use crate::validity::Validity;
const RUN_LEN: usize = 64;
fn array_backed(len: usize) -> Validity {
Validity::from_mask(Mask::from_iter((0..len).map(|i| i % 2 == 0)), Nullable)
}
#[test]
fn test_whole_array_validity_is_kept_as_a_run() {
let mut builder = ValidityBuilder::new(0);
builder.append_validity(array_backed(RUN_LEN), RUN_LEN);
builder.append_validity(array_backed(RUN_LEN), RUN_LEN);
assert_eq!(builder.len(), 2 * RUN_LEN);
let Validity::Array(array) = builder.finish_with_nullability(Nullable) else {
panic!("expected array-backed validity");
};
assert!(
array.is::<Chunked>(),
"the runs should have been concatenated, not copied into one buffer",
);
}
#[test]
fn test_all_valid_runs_stay_lazy() {
let mut builder = ValidityBuilder::new(0);
builder.append_validity(Validity::AllValid, RUN_LEN);
builder.append_validity(Validity::AllValid, RUN_LEN);
assert!(matches!(
builder.finish_with_nullability(Nullable),
Validity::AllValid
));
}
#[test]
fn test_short_validity_is_kept_as_a_run_too() {
let mut builder = ValidityBuilder::new(0);
for _ in 0..RUN_LEN {
builder.append_validity(Validity::AllInvalid, 1);
}
assert_eq!(builder.len(), RUN_LEN);
assert!(matches!(
builder.finish_with_nullability(Nullable),
Validity::AllInvalid
));
}
#[test]
fn test_bits_and_runs_keep_their_order() -> VortexResult<()> {
let mut ctx = array_session().create_execution_ctx();
let mut builder = ValidityBuilder::new(0);
builder.append_n_nulls(1);
builder.append_validity(Validity::AllValid, RUN_LEN);
builder.append_non_null();
builder.append_validity(Validity::AllInvalid, RUN_LEN);
let validity = builder.finish_with_nullability(Nullable);
let mask = validity.execute_mask(2 * RUN_LEN + 2, &mut ctx)?;
let expected = Mask::from_iter(
[false]
.into_iter()
.chain(std::iter::repeat_n(true, RUN_LEN))
.chain([true])
.chain(std::iter::repeat_n(false, RUN_LEN)),
);
assert_eq!(mask, expected);
Ok(())
}
#[test]
fn test_non_nullable_finishes_non_nullable() {
let mut builder = ValidityBuilder::new(0);
builder.append_validity(Validity::NonNullable, RUN_LEN);
builder.append_n_non_nulls(1);
assert!(matches!(
builder.finish_with_nullability(NonNullable),
Validity::NonNullable
));
}
#[test]
fn test_finish_resets_the_builder() {
let mut builder = ValidityBuilder::new(0);
builder.append_validity(Validity::AllInvalid, RUN_LEN);
assert_eq!(builder.finish_with_nullability(Nullable).maybe_len(), None);
assert_eq!(builder.len(), 0);
builder.append_n_nulls(1);
assert!(matches!(
builder.finish_with_nullability(Nullable),
Validity::AllInvalid
));
}
}