1use std::{
5 collections::HashMap,
6 fmt::{self, Display, Formatter},
7 slice,
8 sync::{Arc, LazyLock},
9};
10
11use arrow_array::{
12 Array, ArrayRef, FixedSizeBinaryArray, RecordBatch, RecordBatchOptions, TimestampNanosecondArray, UInt64Array,
13};
14use arrow_schema::{ArrowError, Field, FieldRef, Schema};
15
16use crate::{
17 Result,
18 error::Error,
19 value::{
20 column_view::ColumnView,
21 container::{temporal_array::datetimes, wide_int_array::wides},
22 datetime::DateTime,
23 partition::Partition,
24 row_number::RowNumber,
25 value_type::{
26 ValueType,
27 field::{FieldType, field_error, to_field},
28 },
29 },
30};
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum SystemColumn {
34 RowNumbers,
35 Partitions,
36 CreatedAt,
37 UpdatedAt,
38 Time,
39 CommitVersion,
40}
41
42impl SystemColumn {
43 pub const ALL: [SystemColumn; 6] = [
44 SystemColumn::RowNumbers,
45 SystemColumn::Partitions,
46 SystemColumn::CreatedAt,
47 SystemColumn::UpdatedAt,
48 SystemColumn::Time,
49 SystemColumn::CommitVersion,
50 ];
51
52 pub const fn name(self) -> &'static str {
53 match self {
54 SystemColumn::RowNumbers => "#rownum",
55 SystemColumn::Partitions => "#partition",
56 SystemColumn::CreatedAt => "#created_at",
57 SystemColumn::UpdatedAt => "#updated_at",
58 SystemColumn::Time => "#time",
59 SystemColumn::CommitVersion => "#commit_version",
60 }
61 }
62
63 pub const fn ty(self) -> ValueType {
64 match self {
65 SystemColumn::RowNumbers => ValueType::Uint8,
66 SystemColumn::Partitions => ValueType::Uint16,
67 SystemColumn::CreatedAt => ValueType::DateTime,
68 SystemColumn::UpdatedAt => ValueType::DateTime,
69 SystemColumn::Time => ValueType::DateTime,
70 SystemColumn::CommitVersion => ValueType::Uint8,
71 }
72 }
73
74 pub fn from_name(name: &str) -> Option<SystemColumn> {
75 SystemColumn::ALL.into_iter().find(|column| column.name() == name)
76 }
77
78 fn rank(self) -> usize {
79 SystemColumn::ALL.iter().position(|column| *column == self).expect("every system column is in ALL")
80 }
81}
82
83static SYSTEM_FIELDS: LazyLock<[[FieldRef; 2]; 6]> = LazyLock::new(|| {
84 SystemColumn::ALL.map(|column| {
85 [false, true].map(|holds_nones| {
86 let value_type = match holds_nones {
87 true => ValueType::Option(Box::new(column.ty())),
88 false => column.ty(),
89 };
90 Arc::new(to_field(
91 column.name(),
92 &FieldType {
93 value_type: Some(value_type),
94 ..FieldType::default()
95 },
96 ))
97 })
98 })
99});
100
101pub fn system_field(column: SystemColumn, holds_nones: bool) -> FieldRef {
102 SYSTEM_FIELDS[column.rank()][usize::from(holds_nones)].clone()
103}
104
105impl Display for SystemColumn {
106 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
107 f.write_str(self.name())
108 }
109}
110
111pub fn system_column(batch: &RecordBatch, column: SystemColumn) -> Option<&ArrayRef> {
112 position(batch, column.name()).map(|index| batch.column(index))
113}
114
115pub fn row_numbers(batch: &RecordBatch) -> Result<&[RowNumber]> {
116 let Some(array) = present::<UInt64Array>(batch, SystemColumn::RowNumbers)? else {
117 return Ok(&[]);
118 };
119 let values: &[u64] = array.values();
120 Ok(unsafe { slice::from_raw_parts(values.as_ptr().cast::<RowNumber>(), values.len()) })
122}
123
124pub fn partitions(batch: &RecordBatch) -> Result<Vec<Partition>> {
125 let Some(array) = present::<FixedSizeBinaryArray>(batch, SystemColumn::Partitions)? else {
126 return Ok(Vec::new());
127 };
128 Ok(wides::<u128>(array).into_iter().map(Partition).collect())
129}
130
131pub fn created_at(batch: &RecordBatch) -> Result<&[DateTime]> {
132 datetime_column(batch, SystemColumn::CreatedAt)
133}
134
135pub fn updated_at(batch: &RecordBatch) -> Result<&[DateTime]> {
136 datetime_column(batch, SystemColumn::UpdatedAt)
137}
138
139pub fn time(batch: &RecordBatch) -> Result<&[DateTime]> {
140 datetime_column(batch, SystemColumn::Time)
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub struct RowTimes<'a> {
145 values: &'a [DateTime],
146 defined: Option<(&'a [u8], usize)>,
147}
148
149impl<'a> RowTimes<'a> {
150 pub const EMPTY: Self = Self {
151 values: &[],
152 defined: None,
153 };
154
155 pub fn new(values: &'a [DateTime], defined: Option<(&'a [u8], usize)>) -> Self {
156 Self {
157 values,
158 defined,
159 }
160 }
161
162 pub fn len(&self) -> usize {
163 self.values.len()
164 }
165
166 pub fn is_empty(&self) -> bool {
167 self.values.is_empty()
168 }
169
170 pub fn get(&self, index: usize) -> Option<DateTime> {
171 let value = *self.values.get(index)?;
172 match self.defined {
173 Some((bits, offset)) => {
174 let bit = offset + index;
175 bits.get(bit / 8).is_some_and(|byte| byte & (1 << (bit % 8)) != 0).then_some(value)
176 }
177 None => Some(value),
178 }
179 }
180
181 pub fn iter(&self) -> impl Iterator<Item = Option<DateTime>> + '_ {
182 (0..self.len()).map(|index| self.get(index))
183 }
184
185 pub fn values(&self) -> &'a [DateTime] {
186 self.values
187 }
188
189 pub fn defined(&self) -> Option<(&'a [u8], usize)> {
190 self.defined
191 }
192}
193
194#[allow(clippy::disallowed_methods)]
195pub fn row_times(batch: &RecordBatch) -> Result<RowTimes<'_>> {
196 let Some(array) = system_column(batch, SystemColumn::Time) else {
197 return Ok(RowTimes::EMPTY);
198 };
199 let array = array.as_any().downcast_ref::<TimestampNanosecondArray>().ok_or_else(|| {
200 field_error(format!(
201 "system column {} holds arrow type {}",
202 SystemColumn::Time.name(),
203 array.data_type()
204 ))
205 })?;
206 Ok(RowTimes::new(datetimes(array), array.nulls().map(|nulls| (nulls.validity(), nulls.offset()))))
207}
208
209pub fn require_row_numbers(batch: &RecordBatch) -> Result<&[RowNumber]> {
210 require(batch, SystemColumn::RowNumbers)?;
211 row_numbers(batch)
212}
213
214pub fn require_created_at(batch: &RecordBatch) -> Result<&[DateTime]> {
215 require(batch, SystemColumn::CreatedAt)?;
216 created_at(batch)
217}
218
219pub fn require_updated_at(batch: &RecordBatch) -> Result<&[DateTime]> {
220 require(batch, SystemColumn::UpdatedAt)?;
221 updated_at(batch)
222}
223
224pub fn require_time(batch: &RecordBatch) -> Result<&[DateTime]> {
225 require(batch, SystemColumn::Time)?;
226 time(batch)
227}
228
229pub fn commit_versions(batch: &RecordBatch) -> Result<&[u64]> {
230 match present::<UInt64Array>(batch, SystemColumn::CommitVersion)? {
231 Some(array) => Ok(array.values()),
232 None => Ok(&[]),
233 }
234}
235
236pub fn with_system_column(batch: RecordBatch, column: SystemColumn, array: ArrayRef) -> Result<RecordBatch> {
237 stamp_system_columns(batch, vec![(column, array)])
238}
239
240pub fn stamp_system_columns(batch: RecordBatch, stamps: Vec<(SystemColumn, ArrayRef)>) -> Result<RecordBatch> {
241 let schema = batch.schema();
242 let fields: Vec<FieldRef> = schema.fields().iter().cloned().collect();
243 stamped(schema.metadata().clone(), fields, batch.columns().to_vec(), batch.num_rows(), stamps)
244}
245
246pub fn restamp_row_numbers(batch: &RecordBatch, keep: &[SystemColumn], row_numbers: ArrayRef) -> Result<RecordBatch> {
247 let schema = batch.schema_ref();
248 let (fields, columns): (Vec<FieldRef>, Vec<ArrayRef>) = schema
249 .fields()
250 .iter()
251 .zip(batch.columns())
252 .filter(|(field, _)| SystemColumn::from_name(field.name()).is_none_or(|column| keep.contains(&column)))
253 .map(|(field, array)| (field.clone(), array.clone()))
254 .unzip();
255 stamped(
256 schema.metadata().clone(),
257 fields,
258 columns,
259 batch.num_rows(),
260 vec![(SystemColumn::RowNumbers, row_numbers)],
261 )
262}
263
264fn stamped(
265 metadata: HashMap<String, String>,
266 mut fields: Vec<FieldRef>,
267 mut columns: Vec<ArrayRef>,
268 num_rows: usize,
269 stamps: Vec<(SystemColumn, ArrayRef)>,
270) -> Result<RecordBatch> {
271 let mut row_count = num_rows;
272 for (column, array) in stamps {
273 let field = system_field(column, array.logical_null_count() > 0);
274 if fields.is_empty() && row_count == 0 {
275 row_count = array.len();
276 }
277 match fields.iter().position(|other| other.name() == column.name()) {
278 Some(index) => {
279 fields[index] = field;
280 columns[index] = array;
281 }
282 None => {
283 let index = fields
284 .iter()
285 .position(|other| {
286 SystemColumn::from_name(other.name())
287 .is_some_and(|other| other.rank() > column.rank())
288 })
289 .unwrap_or(fields.len());
290 fields.insert(index, field);
291 columns.insert(index, array);
292 }
293 }
294 }
295 RecordBatch::try_new_with_options(
296 Arc::new(Schema::new_with_metadata(fields, metadata)),
297 columns,
298 &RecordBatchOptions::new().with_row_count(Some(row_count)),
299 )
300 .map_err(arrow_error)
301}
302
303pub fn keep_system_columns(batch: &RecordBatch, keep: &[SystemColumn]) -> Result<RecordBatch> {
304 let indices: Vec<usize> = batch
305 .schema_ref()
306 .fields()
307 .iter()
308 .enumerate()
309 .filter(|(_, field)| SystemColumn::from_name(field.name()).is_none_or(|column| keep.contains(&column)))
310 .map(|(index, _)| index)
311 .collect();
312 batch.project(&indices).map_err(arrow_error)
313}
314
315pub fn is_system_field(field: &Field) -> bool {
316 field.name().starts_with('#')
317}
318
319pub fn user_columns(batch: &RecordBatch) -> impl Iterator<Item = (&FieldRef, &ArrayRef)> {
320 batch.schema_ref().fields().iter().zip(batch.columns()).filter(|(field, _)| !is_system_field(field))
321}
322
323pub fn column_view<'a>(batch: &'a RecordBatch, name: &str) -> Result<Option<ColumnView<'a>>> {
324 match position(batch, name) {
325 Some(index) => ColumnView::try_from((batch.column(index), batch.schema_ref().field(index))).map(Some),
326 None => Ok(None),
327 }
328}
329
330pub fn resolve_column(batch: &RecordBatch, name: &str) -> Option<usize> {
331 let bare = name.strip_prefix('#').unwrap_or(name);
332 SystemColumn::ALL
333 .into_iter()
334 .find(|column| &column.name()[1..] == bare)
335 .and_then(|column| position(batch, column.name()))
336 .or_else(|| position(batch, name))
337}
338
339pub fn check_user_columns(batch: &RecordBatch) -> Result<()> {
340 if batch.num_rows() > 0 && batch.num_columns() == 0 {
341 return Err(field_error(format!("a batch of {} rows has no columns", batch.num_rows())));
342 }
343 Ok(())
344}
345
346fn position(batch: &RecordBatch, name: &str) -> Option<usize> {
347 batch.schema_ref().fields().iter().position(|field| field.name() == name)
348}
349
350fn present<T: 'static>(batch: &RecordBatch, column: SystemColumn) -> Result<Option<&T>> {
351 let Some(array) = system_column(batch, column) else {
352 return Ok(None);
353 };
354 if array.logical_null_count() > 0 {
355 return Err(field_error(format!(
356 "system column {} holds {} none rows",
357 column.name(),
358 array.logical_null_count()
359 )));
360 }
361 array.as_any().downcast_ref::<T>().map(Some).ok_or_else(|| {
362 field_error(format!("system column {} holds arrow type {}", column.name(), array.data_type()))
363 })
364}
365
366fn require(batch: &RecordBatch, column: SystemColumn) -> Result<()> {
367 match system_column(batch, column) {
368 Some(_) => Ok(()),
369 None => Err(field_error(format!("system column {} is missing", column.name()))),
370 }
371}
372
373fn datetime_column(batch: &RecordBatch, column: SystemColumn) -> Result<&[DateTime]> {
374 match present::<TimestampNanosecondArray>(batch, column)? {
375 Some(array) => Ok(datetimes(array)),
376 None => Ok(&[]),
377 }
378}
379
380fn arrow_error(error: ArrowError) -> Error {
381 field_error(error.to_string())
382}
383
384#[cfg(test)]
385mod tests {
386 use arrow_array::{Int32Array, UInt64Array};
387 use arrow_schema::DataType;
388
389 use super::*;
390 use crate::value::{
391 Value,
392 container::{temporal_array::datetime_array, wide_int_array::wide_array},
393 };
394
395 fn user(name: &str, values: Vec<i32>) -> (FieldRef, ArrayRef) {
396 let field = to_field(
397 name,
398 &FieldType {
399 value_type: Some(ValueType::Int4),
400 ..FieldType::default()
401 },
402 );
403 (Arc::new(field), Arc::new(Int32Array::from(values)))
404 }
405
406 fn batch(columns: Vec<(FieldRef, ArrayRef)>) -> RecordBatch {
407 let (fields, arrays): (Vec<FieldRef>, Vec<ArrayRef>) = columns.into_iter().unzip();
408 RecordBatch::try_new(Arc::new(Schema::new(fields)), arrays).unwrap()
409 }
410
411 fn names(batch: &RecordBatch) -> Vec<String> {
412 batch.schema_ref().fields().iter().map(|field| field.name().clone()).collect()
413 }
414
415 fn stamps(values: Vec<i64>) -> ArrayRef {
416 Arc::new(datetime_array(values.into_iter().map(DateTime::from_nanos)))
417 }
418
419 #[test]
420 fn row_numbers_read_the_rownum_column_and_are_empty_when_it_is_absent() {
421 let plain = batch(vec![user("a", vec![1, 2])]);
423 assert!(row_numbers(&plain).unwrap().is_empty());
424 let numbered =
425 with_system_column(plain, SystemColumn::RowNumbers, Arc::new(UInt64Array::from(vec![7u64, 9])))
426 .unwrap();
427 assert_eq!(row_numbers(&numbered).unwrap(), &[RowNumber(7), RowNumber(9)]);
428 }
429
430 #[test]
431 fn partitions_decode_the_sixteen_byte_column() {
432 let wide: ArrayRef = Arc::new(wide_array([1u128 << 100, 3]));
434 let with =
435 with_system_column(batch(vec![user("a", vec![1, 2])]), SystemColumn::Partitions, wide).unwrap();
436 assert_eq!(partitions(&with).unwrap(), vec![Partition(1u128 << 100), Partition(3)]);
437 }
438
439 #[test]
440 fn datetime_getters_read_their_own_column() {
441 let base = batch(vec![user("a", vec![1])]);
443 let base = with_system_column(base, SystemColumn::CreatedAt, stamps(vec![10])).unwrap();
444 let base = with_system_column(base, SystemColumn::UpdatedAt, stamps(vec![20])).unwrap();
445 let base = with_system_column(base, SystemColumn::Time, stamps(vec![30])).unwrap();
446 let base =
447 with_system_column(base, SystemColumn::CommitVersion, Arc::new(UInt64Array::from(vec![5u64])))
448 .unwrap();
449 assert_eq!(created_at(&base).unwrap(), &[DateTime::from_nanos(10)]);
450 assert_eq!(updated_at(&base).unwrap(), &[DateTime::from_nanos(20)]);
451 assert_eq!(time(&base).unwrap(), &[DateTime::from_nanos(30)]);
452 assert_eq!(commit_versions(&base).unwrap(), &[5]);
453 }
454
455 #[test]
456 fn a_getter_rejects_a_none_row() {
457 let mixed: ArrayRef =
459 Arc::new(TimestampNanosecondArray::from(vec![Some(1), None]).with_timezone("+00:00"));
460 let with = with_system_column(batch(vec![user("a", vec![1, 2])]), SystemColumn::Time, mixed).unwrap();
461 assert!(with.schema_ref().field_with_name("#time").unwrap().is_nullable());
462 assert!(time(&with).is_err());
463 }
464
465 #[test]
466 fn a_getter_rejects_a_column_of_the_wrong_arrow_type() {
467 let (_, wrong) = user("#rownum", vec![1]);
469 let field = Arc::new(Field::new("#rownum", wrong.data_type().clone(), false));
470 let bad = batch(vec![(field, wrong)]);
471 assert!(row_numbers(&bad).is_err());
472 }
473
474 #[test]
475 fn system_columns_are_inserted_after_user_columns_in_all_order() {
476 let base = batch(vec![user("a", vec![1]), user("b", vec![2])]);
478 let base = with_system_column(base, SystemColumn::Time, stamps(vec![3])).unwrap();
479 let base = with_system_column(base, SystemColumn::RowNumbers, Arc::new(UInt64Array::from(vec![1u64])))
480 .unwrap();
481 let base = with_system_column(base, SystemColumn::CreatedAt, stamps(vec![4])).unwrap();
482 assert_eq!(names(&base), ["a", "b", "#rownum", "#created_at", "#time"]);
483 }
484
485 #[test]
486 fn a_present_system_column_is_replaced_in_place() {
487 let base = with_system_column(
489 batch(vec![user("a", vec![1])]),
490 SystemColumn::RowNumbers,
491 Arc::new(UInt64Array::from(vec![1u64])),
492 )
493 .unwrap();
494 let replaced =
495 with_system_column(base, SystemColumn::RowNumbers, Arc::new(UInt64Array::from(vec![8u64])))
496 .unwrap();
497 assert_eq!(names(&replaced), ["a", "#rownum"]);
498 assert_eq!(row_numbers(&replaced).unwrap(), &[RowNumber(8)]);
499 }
500
501 #[test]
502 fn a_system_column_on_an_empty_batch_sets_the_row_count() {
503 let empty = RecordBatch::new_empty(Arc::new(Schema::empty()));
505 let with = with_system_column(
506 empty,
507 SystemColumn::RowNumbers,
508 Arc::new(UInt64Array::from(vec![1u64, 2, 3])),
509 )
510 .unwrap();
511 assert_eq!(with.num_rows(), 3);
512 }
513
514 #[test]
515 fn a_system_column_of_the_wrong_length_is_rejected() {
516 let result = with_system_column(
518 batch(vec![user("a", vec![1, 2])]),
519 SystemColumn::RowNumbers,
520 Arc::new(UInt64Array::from(vec![1u64])),
521 );
522 assert!(result.is_err());
523 }
524
525 #[test]
526 fn keep_drops_only_unnamed_system_columns() {
527 let op = Arc::new(Field::new("#op", DataType::Int32, false));
529 let base = batch(vec![user("a", vec![1]), (op, Arc::new(Int32Array::from(vec![1])))]);
530 let base = with_system_column(base, SystemColumn::RowNumbers, Arc::new(UInt64Array::from(vec![1u64])))
531 .unwrap();
532 let base = with_system_column(base, SystemColumn::CreatedAt, stamps(vec![1])).unwrap();
533 let base = with_system_column(base, SystemColumn::Time, stamps(vec![1])).unwrap();
534 let kept = keep_system_columns(&base, &[SystemColumn::RowNumbers, SystemColumn::Time]).unwrap();
535 assert_eq!(names(&kept), ["a", "#op", "#rownum", "#time"]);
536 assert_eq!(kept.num_rows(), 1);
537 }
538
539 #[test]
540 fn a_bare_name_resolves_to_the_system_column_first() {
541 let base = batch(vec![user("rownum", vec![5]), user("x", vec![6])]);
543 let base = with_system_column(base, SystemColumn::RowNumbers, Arc::new(UInt64Array::from(vec![1u64])))
544 .unwrap();
545 assert_eq!(resolve_column(&base, "rownum"), Some(2));
546 assert_eq!(resolve_column(&base, "#rownum"), Some(2));
547 assert_eq!(resolve_column(&base, "x"), Some(1));
548 assert_eq!(resolve_column(&base, "created_at"), None);
549 assert_eq!(resolve_column(&base, "missing"), None);
550 }
551
552 #[test]
553 fn rows_without_any_column_are_rejected() {
554 let counted = RecordBatch::try_new_with_options(
556 Arc::new(Schema::empty()),
557 vec![],
558 &RecordBatchOptions::new().with_row_count(Some(2)),
559 )
560 .unwrap();
561 assert!(check_user_columns(&counted).is_err());
562 let named = with_system_column(
563 RecordBatch::new_empty(Arc::new(Schema::empty())),
564 SystemColumn::RowNumbers,
565 Arc::new(UInt64Array::from(vec![1u64, 2])),
566 )
567 .unwrap();
568 assert!(check_user_columns(&named).is_ok());
569 assert!(check_user_columns(&RecordBatch::new_empty(Arc::new(Schema::empty()))).is_ok());
570 }
571
572 #[test]
573 fn a_hash_prefix_marks_a_system_field() {
574 assert!(is_system_field(&Field::new("#rownum", DataType::UInt64, false)));
576 assert!(is_system_field(&Field::new("#op", DataType::Int32, false)));
577 assert!(!is_system_field(&Field::new("rownum", DataType::UInt64, false)));
578 }
579
580 #[test]
581 fn column_view_finds_a_column_by_its_exact_name() {
582 let base = batch(vec![user("rownum", vec![5])]);
584 let base = with_system_column(base, SystemColumn::RowNumbers, Arc::new(UInt64Array::from(vec![9u64])))
585 .unwrap();
586 let user_view = column_view(&base, "rownum").unwrap().unwrap();
587 assert_eq!(user_view.get_value(0), Value::Int4(5));
588 let system_view = column_view(&base, "#rownum").unwrap().unwrap();
589 assert_eq!(system_view.get_value(0), Value::Uint8(9));
590 assert!(column_view(&base, "missing").unwrap().is_none());
591 }
592
593 #[test]
594 fn require_getters_fail_naming_the_absent_column() {
595 let plain = batch(vec![user("a", vec![1])]);
597 for (result, name) in [
598 (require_row_numbers(&plain).map(|_| ()), "#rownum"),
599 (require_created_at(&plain).map(|_| ()), "#created_at"),
600 (require_updated_at(&plain).map(|_| ()), "#updated_at"),
601 (require_time(&plain).map(|_| ()), "#time"),
602 ] {
603 let message = format!("{:?}", result.unwrap_err());
604 assert!(message.contains(name), "{name} missing from {message}");
605 }
606 }
607
608 #[test]
609 fn require_getters_read_a_present_column() {
610 let base = batch(vec![user("a", vec![1])]);
612 let base = with_system_column(base, SystemColumn::RowNumbers, Arc::new(UInt64Array::from(vec![4u64])))
613 .unwrap();
614 let base = with_system_column(base, SystemColumn::CreatedAt, stamps(vec![10])).unwrap();
615 let base = with_system_column(base, SystemColumn::UpdatedAt, stamps(vec![20])).unwrap();
616 let base = with_system_column(base, SystemColumn::Time, stamps(vec![30])).unwrap();
617 assert_eq!(require_row_numbers(&base).unwrap(), &[RowNumber(4)]);
618 assert_eq!(require_created_at(&base).unwrap(), &[DateTime::from_nanos(10)]);
619 assert_eq!(require_updated_at(&base).unwrap(), &[DateTime::from_nanos(20)]);
620 assert_eq!(require_time(&base).unwrap(), &[DateTime::from_nanos(30)]);
621 }
622
623 #[test]
624 fn require_getters_still_reject_a_none_row() {
625 let mixed: ArrayRef =
627 Arc::new(TimestampNanosecondArray::from(vec![Some(1), None]).with_timezone("+00:00"));
628 let with = with_system_column(batch(vec![user("a", vec![1, 2])]), SystemColumn::Time, mixed).unwrap();
629 assert!(require_time(&with).is_err());
630 }
631
632 #[test]
633 fn user_columns_skip_every_hash_column_in_order() {
634 let op = Arc::new(Field::new("#op", DataType::Int32, false));
636 let base =
637 batch(vec![user("a", vec![1]), (op, Arc::new(Int32Array::from(vec![2]))), user("b", vec![3])]);
638 let base = with_system_column(base, SystemColumn::RowNumbers, Arc::new(UInt64Array::from(vec![1u64])))
639 .unwrap();
640 let names: Vec<&str> = user_columns(&base).map(|(field, _)| field.name().as_str()).collect();
641 assert_eq!(names, ["a", "b"]);
642 let (_, b) = user_columns(&base).nth(1).unwrap();
643 assert_eq!(b.as_any().downcast_ref::<Int32Array>().unwrap().value(0), 3);
644 assert_eq!(user_columns(&RecordBatch::new_empty(Arc::new(Schema::empty()))).count(), 0);
645 }
646}