From c99ec561e50b4f1a279162c9109f3cf87c16ddbf Mon Sep 17 00:00:00 2001 From: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com> Date: Tue, 3 Nov 2026 13:06:23 +0000 Subject: [PATCH] Wrap legacy --fbin vector index instead of panicking past the end of the file --- src/generators/legacy.rs | 54 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/generators/legacy.rs b/src/generators/legacy.rs index f10db16..3c5576d 100644 --- a/src/generators/legacy.rs +++ b/src/generators/legacy.rs @@ -45,7 +45,10 @@ impl PointGenerator for LegacyGenerator { }; let vectors: Vectors = if let Some(reader) = &self.reader { - reader.read_vector(idx as usize).to_vec().into() + // Cycle through the file when more points are requested than it holds, + // like `source: {type: file}` does in YAML configs. + let n = reader.num_vectors.max(1) as usize; + reader.read_vector(idx as usize % n).to_vec().into() } else if self.args.vectors_per_point != 1 { let vectors_map: HashMap<_, _> = (0..self.args.vectors_per_point) .map(|i| (format!("{i}"), random_vector(&mut rng, &self.args))) @@ -96,3 +99,52 @@ impl PointGenerator for LegacyGenerator { random_payload(&mut rng, &self.args, self.zipf.as_ref()) } } + +#[cfg(test)] +mod tests { + use std::io::Write; + + use clap::Parser; + use qdrant_client::qdrant::vector::Vector as VectorVariant; + + use super::*; + + /// Write `vectors` to a temporary file in fbin layout. + fn fbin_file(vectors: &[Vec]) -> tempfile::NamedTempFile { + let mut file = tempfile::NamedTempFile::new().unwrap(); + file.write_all(&(vectors.len() as i32).to_le_bytes()) + .unwrap(); + file.write_all(&(vectors[0].len() as i32).to_le_bytes()) + .unwrap(); + for value in vectors.iter().flatten() { + file.write_all(&value.to_le_bytes()).unwrap(); + } + file.flush().unwrap(); + file + } + + fn dense(point: PointStruct) -> Vec { + match point.vectors.unwrap().vectors_options.unwrap() { + VectorsOptions::Vector(vector) => match vector.vector.unwrap() { + VectorVariant::Dense(dense) => dense.data, + _ => panic!("expected a dense vector"), + }, + _ => panic!("expected the unnamed default vector"), + } + } + + /// `--fbin` with more points (or a larger `--offset`) than the file holds + /// must cycle through the file instead of panicking on an out-of-range read. + #[test] + fn fbin_vectors_wrap_past_the_end_of_the_file() { + let file = fbin_file(&[vec![1.0, 2.0], vec![3.0, 4.0]]); + let reader = FBinReader::new(file.path()).unwrap(); + let args = Args::parse_from(["bfb", "--dim", "2"]); + let generator = LegacyGenerator::new(args, Some(reader)); + + assert_eq!(dense(generator.make_point(0)), [1.0, 2.0]); + assert_eq!(dense(generator.make_point(1)), [3.0, 4.0]); + assert_eq!(dense(generator.make_point(2)), [1.0, 2.0]); + assert_eq!(dense(generator.make_point(5)), [3.0, 4.0]); + } +}