1use arrow_schema::{DataType, Field, Schema};
4use iris_abi::Node;
5
6use crate::error::{Invariant, Result, Violation};
7use crate::layout::{Layout, layout, offset_width, slot_bits};
8
9pub const MAX_DEPTH: usize = 64;
17
18pub fn check_schema(schema: &Schema) -> Result<()> {
29 let mut work: Vec<(&Field, usize, String)> = schema
33 .fields()
34 .iter()
35 .map(|field| (field.as_ref(), 1, field.name().clone()))
36 .collect();
37
38 while let Some((field, depth, path)) = work.pop() {
39 if depth > MAX_DEPTH {
40 return Err(Violation::at(
41 Invariant::Depth,
42 &path,
43 format!("this build walks {MAX_DEPTH} levels of nesting and this is deeper"),
44 ));
45 }
46 let found = layout(field.data_type(), &path)?;
47 for child in found.children {
48 let child_path = format!("{path}.{}", child.name());
49 work.push((child, depth + 1, child_path));
50 }
51 }
52
53 Ok(())
54}
55
56pub fn check<B: AsRef<[u8]>>(
73 schema: &Schema,
74 rows: u64,
75 nodes: &[Node],
76 buffers: &[B],
77) -> Result<()> {
78 check_schema(schema)?;
79
80 let mut cursor = Cursor {
81 nodes,
82 buffers,
83 node: 0,
84 buffer: 0,
85 };
86
87 for field in schema.fields() {
88 let len = cursor.array(field, field.name())?;
89 if len != rows {
90 return Err(Violation::at(
91 Invariant::Rows,
92 field.name(),
93 format!("the batch says {rows} rows and this column has {len}"),
94 ));
95 }
96 }
97
98 cursor.finish()
99}
100
101struct Cursor<'a, B> {
103 nodes: &'a [Node],
104 buffers: &'a [B],
105 node: usize,
106 buffer: usize,
107}
108
109impl<'a, B: AsRef<[u8]>> Cursor<'a, B> {
110 fn array(&mut self, field: &Field, path: &str) -> Result<u64> {
112 let node = self.next_node(path)?;
113 let data_type = field.data_type();
114 let Layout {
115 validity,
116 values,
117 children,
118 } = layout(data_type, path)?;
119
120 if validity {
121 let bitmap = self.next_buffer(path)?;
122 check_validity(bitmap, node.length, node.null_count, path)?;
123 } else if node.null_count != 0 {
124 return Err(Violation::at(
125 Invariant::NullCount,
126 path,
127 format!(
128 "a {data_type} column has no validity buffer and this one says it has {} nulls",
129 node.null_count
130 ),
131 ));
132 }
133
134 let mut taken = Vec::with_capacity(values);
135 for _ in 0..values {
136 taken.push(self.next_buffer(path)?);
137 }
138
139 let mut child_lengths = Vec::with_capacity(children.len());
142 for child in &children {
143 let child_path = format!("{path}.{}", child.name());
144 child_lengths.push(self.array(child, &child_path)?);
145 }
146
147 check_values(data_type, node.length, &taken, &child_lengths, path)?;
148 Ok(node.length)
149 }
150
151 fn next_node(&mut self, path: &str) -> Result<Node> {
152 let node = self.nodes.get(self.node).copied().ok_or_else(|| {
153 Violation::at(
154 Invariant::Arrays,
155 path,
156 format!(
157 "the schema calls for more arrays than the batch has, which is {}",
158 self.nodes.len()
159 ),
160 )
161 })?;
162 self.node += 1;
163 Ok(node)
164 }
165
166 fn next_buffer(&mut self, path: &str) -> Result<&'a [u8]> {
167 let bytes = self.buffers.get(self.buffer).ok_or_else(|| {
168 Violation::at(
169 Invariant::Buffers,
170 path,
171 format!(
172 "the schema calls for more buffers than the batch has, which is {}",
173 self.buffers.len()
174 ),
175 )
176 })?;
177 self.buffer += 1;
178 Ok(bytes.as_ref())
179 }
180
181 fn finish(&self) -> Result<()> {
187 if self.node != self.nodes.len() {
188 return Err(Violation::at(
189 Invariant::Arrays,
190 "",
191 format!(
192 "the batch has {} arrays and the schema accounts for {}",
193 self.nodes.len(),
194 self.node
195 ),
196 ));
197 }
198 if self.buffer != self.buffers.len() {
199 return Err(Violation::at(
200 Invariant::Buffers,
201 "",
202 format!(
203 "the batch has {} buffers and the schema accounts for {}",
204 self.buffers.len(),
205 self.buffer
206 ),
207 ));
208 }
209 Ok(())
210 }
211}
212
213fn check_validity(bitmap: &[u8], len: u64, null_count: u64, path: &str) -> Result<()> {
218 if bitmap.is_empty() {
219 if null_count != 0 {
220 return Err(Violation::at(
221 Invariant::NullCount,
222 path,
223 format!(
224 "there is no validity buffer and this array says it has {null_count} nulls"
225 ),
226 ));
227 }
228 return Ok(());
229 }
230
231 let needed = len.div_ceil(8);
232 let have = as_u64(bitmap.len());
233 if have < needed {
234 return Err(Violation::at(
235 Invariant::Validity,
236 path,
237 format!("{len} slots need {needed} bytes of validity and there are {have}"),
238 ));
239 }
240
241 let counted = count_nulls(bitmap, len);
245 if counted != null_count {
246 return Err(Violation::at(
247 Invariant::NullCount,
248 path,
249 format!("this array says it has {null_count} nulls and its bitmap has {counted}"),
250 ));
251 }
252
253 Ok(())
254}
255
256fn count_nulls(bitmap: &[u8], len: u64) -> u64 {
258 let mut nulls = 0;
259 let mut seen = 0u64;
260 for byte in bitmap {
261 if seen >= len {
262 break;
263 }
264 let left = len - seen;
265 let bits = if left >= 8 {
266 8
267 } else {
268 u32::try_from(left).unwrap_or(8)
269 };
270 let mask: u8 = if bits == 8 {
271 u8::MAX
272 } else {
273 (1u8 << bits) - 1
274 };
275 nulls += u64::from(bits) - u64::from((byte & mask).count_ones());
276 seen += u64::from(bits);
277 }
278 nulls
279}
280
281fn check_values(
286 data_type: &DataType,
287 len: u64,
288 buffers: &[&[u8]],
289 child_lengths: &[u64],
290 path: &str,
291) -> Result<()> {
292 let child = child_lengths.first().copied().unwrap_or(0);
293
294 match data_type {
295 DataType::Null => Ok(()),
296 DataType::Utf8 | DataType::Binary | DataType::LargeUtf8 | DataType::LargeBinary => {
297 check_variable(data_type, len, buffers, path)
298 }
299 DataType::List(_) | DataType::LargeList(_) | DataType::Map(_, _) => {
300 check_list(data_type, len, buffers, child, path)
301 }
302 DataType::FixedSizeList(_, size) => check_fixed_size_list(len, *size, child, path),
303 DataType::Struct(fields) => {
304 for (field, child) in fields.iter().zip(child_lengths) {
305 if *child < len {
306 return Err(Violation::at(
307 Invariant::ChildLength,
308 path,
309 format!(
310 "this struct has {len} rows and its {} field has {child}",
311 field.name()
312 ),
313 ));
314 }
315 }
316 Ok(())
317 }
318 other => check_fixed_width(other, len, buffers, path),
320 }
321}
322
323fn check_variable(data_type: &DataType, len: u64, buffers: &[&[u8]], path: &str) -> Result<()> {
325 let [offsets, data] = buffers else {
326 return Err(counted_wrong(path, 2, buffers.len()));
327 };
328 let width = offset_width(data_type).expect("a variable length type has offsets");
329 let last = check_offsets(offsets, len, width, path)?;
330 let have = as_u64(data.len());
331 if last > have {
332 return Err(Violation::at(
333 Invariant::OffsetRange,
334 path,
335 format!("the last offset is {last} and the values buffer is {have} bytes"),
336 ));
337 }
338 Ok(())
339}
340
341fn check_list(
343 data_type: &DataType,
344 len: u64,
345 buffers: &[&[u8]],
346 child: u64,
347 path: &str,
348) -> Result<()> {
349 let [offsets] = buffers else {
350 return Err(counted_wrong(path, 1, buffers.len()));
351 };
352 let width = offset_width(data_type).expect("a list has offsets");
353 let last = check_offsets(offsets, len, width, path)?;
354 if last > child {
355 return Err(Violation::at(
356 Invariant::OffsetRange,
357 path,
358 format!("the last offset is {last} and the child array has {child} slots"),
359 ));
360 }
361 Ok(())
362}
363
364fn check_fixed_size_list(len: u64, size: i32, child: u64, path: &str) -> Result<()> {
366 let size = u64::try_from(size).map_err(|_| {
367 Violation::at(
368 Invariant::ChildLength,
369 path,
370 format!("a fixed size list cannot hold {size} values a row"),
371 )
372 })?;
373 let needed = len.checked_mul(size).ok_or_else(|| {
374 Violation::at(
375 Invariant::Size,
376 path,
377 format!("{len} rows of {size} values is more than this host can address"),
378 )
379 })?;
380 if child < needed {
381 return Err(Violation::at(
382 Invariant::ChildLength,
383 path,
384 format!("{len} rows of {size} values need {needed} slots and the child has {child}"),
385 ));
386 }
387 Ok(())
388}
389
390fn check_fixed_width(data_type: &DataType, len: u64, buffers: &[&[u8]], path: &str) -> Result<()> {
392 let [values] = buffers else {
393 return Err(counted_wrong(path, 1, buffers.len()));
394 };
395 let bits = slot_bits(data_type).ok_or_else(|| {
396 Violation::at(
397 Invariant::Unsupported,
398 path,
399 format!("this build does not know how wide a {data_type} slot is"),
400 )
401 })?;
402 let needed = len
403 .checked_mul(bits)
404 .map(|total| total.div_ceil(8))
405 .ok_or_else(|| {
406 Violation::at(
407 Invariant::Size,
408 path,
409 format!("{len} slots of {bits} bits is more than this host can address"),
410 )
411 })?;
412 let have = as_u64(values.len());
413 if have < needed {
414 return Err(Violation::at(
415 Invariant::BufferLength,
416 path,
417 format!("{len} slots of {bits} bits need {needed} bytes and there are {have}"),
418 ));
419 }
420 Ok(())
421}
422
423fn check_offsets(offsets: &[u8], len: u64, width: u64, path: &str) -> Result<u64> {
428 if len == 0 && offsets.is_empty() {
431 return Ok(0);
432 }
433
434 let entries = len.checked_add(1).ok_or_else(|| {
439 Violation::at(
440 Invariant::Size,
441 path,
442 format!("{len} slots need one more offset than that, which does not fit in a count"),
443 )
444 })?;
445 let needed = entries.checked_mul(width).ok_or_else(|| {
446 Violation::at(
447 Invariant::Size,
448 path,
449 format!("{entries} offsets of {width} bytes is more than this host can address"),
450 )
451 })?;
452 let have = as_u64(offsets.len());
453 if have < needed {
454 return Err(Violation::at(
455 Invariant::BufferLength,
456 path,
457 format!("{len} slots need {needed} bytes of offsets and there are {have}"),
458 ));
459 }
460
461 let mut previous: i64 = 0;
462 for index in 0..entries {
463 let at = usize::try_from(index * width).map_err(|_| {
464 Violation::at(
465 Invariant::Size,
466 path,
467 "the offsets run past what this host can address".to_owned(),
468 )
469 })?;
470 let offset = read_offset(offsets, at, width);
471
472 if offset < 0 {
473 return Err(Violation::at(
474 Invariant::OffsetRange,
475 path,
476 format!("offset {index} is {offset}, and an offset is a position"),
477 ));
478 }
479 if index > 0 && offset < previous {
480 return Err(Violation::at(
481 Invariant::OffsetOrder,
482 path,
483 format!("offset {index} is {offset} and the one before it is {previous}"),
484 ));
485 }
486 previous = offset;
487 }
488
489 u64::try_from(previous).map_err(|_| {
490 Violation::at(
491 Invariant::OffsetRange,
492 path,
493 "the last offset is negative".to_owned(),
494 )
495 })
496}
497
498fn read_offset(bytes: &[u8], at: usize, width: u64) -> i64 {
500 if width == 8 {
501 let mut raw = [0u8; 8];
502 raw.copy_from_slice(&bytes[at..at + 8]);
503 i64::from_le_bytes(raw)
504 } else {
505 let mut raw = [0u8; 4];
506 raw.copy_from_slice(&bytes[at..at + 4]);
507 i64::from(i32::from_le_bytes(raw))
508 }
509}
510
511fn as_u64(len: usize) -> u64 {
513 u64::try_from(len).unwrap_or(u64::MAX)
514}
515
516fn counted_wrong(path: &str, wanted: usize, found: usize) -> Violation {
517 Violation::at(
518 Invariant::Buffers,
519 path,
520 format!("this column takes {wanted} buffers after its validity buffer and got {found}"),
521 )
522}
523
524#[cfg(test)]
525mod tests {
526 use arrow_schema::{DataType, Field, Fields, Schema};
527 use iris_abi::Node;
528
529 use super::{MAX_DEPTH, check, check_schema, count_nulls};
530 use crate::error::Invariant;
531
532 fn node(length: u64, null_count: u64) -> Node {
533 Node { length, null_count }
534 }
535
536 fn i64s(values: &[i64]) -> Vec<u8> {
537 values.iter().flat_map(|v| v.to_le_bytes()).collect()
538 }
539
540 fn i32s(values: &[i32]) -> Vec<u8> {
541 values.iter().flat_map(|v| v.to_le_bytes()).collect()
542 }
543
544 #[test]
545 fn a_sound_batch_passes() {
546 let schema = Schema::new(vec![Field::new("a", DataType::Int64, false)]);
547 let buffers = vec![Vec::new(), i64s(&[1, 2, 3])];
548 check(&schema, 3, &[node(3, 0)], &buffers).expect("this batch is sound");
549 }
550
551 #[test]
552 fn a_column_shorter_than_the_batch_is_caught() {
553 let schema = Schema::new(vec![Field::new("a", DataType::Int64, false)]);
554 let buffers = vec![Vec::new(), i64s(&[1, 2])];
555 let err = check(&schema, 3, &[node(2, 0)], &buffers).expect_err("two is not three");
556 assert_eq!(err.invariant, Invariant::Rows);
557 }
558
559 #[test]
560 fn a_values_buffer_one_slot_short_is_caught() {
561 let schema = Schema::new(vec![Field::new("a", DataType::Int64, false)]);
562 let buffers = vec![Vec::new(), i64s(&[1, 2])];
563 let err =
564 check(&schema, 3, &[node(3, 0)], &buffers).expect_err("three slots need 24 bytes");
565 assert_eq!(err.invariant, Invariant::BufferLength);
566 assert!(err.to_string().contains("24 bytes"), "{err}");
567 }
568
569 #[test]
570 fn a_bitmap_with_too_few_bits_is_caught() {
571 let schema = Schema::new(vec![Field::new("a", DataType::Int64, true)]);
572 let buffers = vec![Vec::new(), i64s(&[1, 2, 3])];
573 let short = vec![vec![0xffu8], i64s(&[1; 100])];
575 let wide = Schema::new(vec![Field::new("a", DataType::Int64, true)]);
576 let err = check(&wide, 100, &[node(100, 0)], &short).expect_err("100 slots need 13 bytes");
577 assert_eq!(err.invariant, Invariant::Validity);
578 check(&schema, 3, &[node(3, 0)], &buffers).expect("the empty bitmap case still passes");
579 }
580
581 #[test]
582 fn an_offset_one_past_the_end_is_caught() {
583 let schema = Schema::new(vec![Field::new("s", DataType::Utf8, false)]);
584 let buffers = vec![Vec::new(), i32s(&[0, 2, 6]), b"hoyea".to_vec()];
585 let err = check(&schema, 2, &[node(2, 0)], &buffers).expect_err("six is past five");
586 assert_eq!(err.invariant, Invariant::OffsetRange);
587 }
588
589 #[test]
590 fn offsets_that_run_backwards_are_caught() {
591 let schema = Schema::new(vec![Field::new("s", DataType::Utf8, false)]);
592 let buffers = vec![Vec::new(), i32s(&[0, 4, 2]), b"hoyea".to_vec()];
593 let err = check(&schema, 2, &[node(2, 0)], &buffers).expect_err("two is less than four");
594 assert_eq!(err.invariant, Invariant::OffsetOrder);
595 }
596
597 #[test]
598 fn a_child_one_row_short_of_its_parent_is_caught() {
599 let children = Fields::from(vec![Field::new("x", DataType::Int64, false)]);
600 let schema = Schema::new(vec![Field::new("p", DataType::Struct(children), false)]);
601 let buffers = vec![Vec::new(), Vec::new(), i64s(&[1, 2])];
602 let err = check(&schema, 3, &[node(3, 0), node(2, 0)], &buffers)
603 .expect_err("a struct's child cannot be shorter than the struct");
604 assert_eq!(err.invariant, Invariant::ChildLength);
605 }
606
607 #[test]
608 fn a_length_that_overflows_a_width_is_caught_rather_than_wrapped() {
609 let schema = Schema::new(vec![Field::new("a", DataType::Int64, false)]);
610 let buffers = vec![Vec::new(), i64s(&[1])];
611 let err = check(&schema, u64::MAX, &[node(u64::MAX, 0)], &buffers)
612 .expect_err("that many slots is not addressable");
613 assert_eq!(err.invariant, Invariant::Size);
614 }
615
616 #[test]
623 fn a_length_that_wraps_the_count_of_offsets_is_caught() {
624 for data_type in [DataType::Binary, DataType::LargeBinary] {
625 let schema = Schema::new(vec![Field::new("a", data_type, false)]);
626 let buffers = vec![Vec::new(), Vec::new(), Vec::new()];
627 let err = check(&schema, u64::MAX, &[node(u64::MAX, 0)], &buffers)
628 .expect_err("one more offset than that does not fit in a count");
629 assert_eq!(err.invariant, Invariant::Size);
630 }
631 }
632
633 #[test]
634 fn a_schema_nested_past_the_bound_is_refused_without_recursing_into_it() {
635 let mut data_type = DataType::Int64;
636 for _ in 0..MAX_DEPTH + 10 {
637 data_type = DataType::List(std::sync::Arc::new(Field::new("item", data_type, false)));
638 }
639 let schema = Schema::new(vec![Field::new("deep", data_type, false)]);
640 let err = check_schema(&schema).expect_err("that is deeper than this build walks");
641 assert_eq!(err.invariant, Invariant::Depth);
642 }
643
644 #[test]
645 fn a_schema_at_the_bound_is_still_walked() {
646 let mut data_type = DataType::Int64;
647 for _ in 0..MAX_DEPTH - 1 {
648 data_type = DataType::List(std::sync::Arc::new(Field::new("item", data_type, false)));
649 }
650 let schema = Schema::new(vec![Field::new("deep", data_type, false)]);
651 check_schema(&schema).expect("this is exactly as deep as the bound allows");
652 }
653
654 #[test]
655 fn spare_buffers_are_an_error_rather_than_something_ignored() {
656 let schema = Schema::new(vec![Field::new("a", DataType::Int64, false)]);
657 let buffers = vec![Vec::new(), i64s(&[1, 2, 3]), i64s(&[4])];
658 let err =
659 check(&schema, 3, &[node(3, 0)], &buffers).expect_err("a spare buffer is not fine");
660 assert_eq!(err.invariant, Invariant::Buffers);
661 }
662
663 #[test]
664 fn counting_nulls_stops_at_the_length_rather_than_the_byte() {
665 assert_eq!(count_nulls(&[0b0001_1111], 5), 0);
667 assert_eq!(count_nulls(&[0b0001_1110], 5), 1);
668 assert_eq!(count_nulls(&[0x00, 0xff], 9), 8);
669 }
670}