Skip to main content

risingwave_common/array/
data_chunk.rs

1// Copyright 2022 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt;
16use std::fmt::Display;
17use std::hash::BuildHasher;
18use std::sync::Arc;
19
20use bytes::Bytes;
21use comfy_table::{ContentLineStyle, LineStyle, TableStyle};
22use either::Either;
23use itertools::Itertools;
24use rand::rngs::SmallRng;
25use rand::{Rng, SeedableRng};
26use risingwave_common_estimate_size::EstimateSize;
27use risingwave_pb::data::PbDataChunk;
28
29use super::{Array, ArrayImpl, ArrayRef, ArrayResult, StructArray};
30use crate::array::ArrayBuilderImpl;
31use crate::array::data_chunk_iter::RowRef;
32use crate::bitmap::{Bitmap, BitmapBuilder};
33use crate::field_generator::{FieldGeneratorImpl, VarcharProperty};
34use crate::hash::HashCode;
35use crate::row::Row;
36use crate::types::{DataType, DatumRef, MapType, StructType, ToOwnedDatum, ToText};
37use crate::util::chunk_coalesce::DataChunkBuilder;
38use crate::util::hash_util::finalize_hashers;
39use crate::util::iter_util::ZipEqFast;
40use crate::util::value_encoding::{
41    ValueRowSerializer, estimate_serialize_datum_size, serialize_datum_into,
42    try_get_exact_serialize_datum_size,
43};
44
45/// [`DataChunk`] is a collection of Columns,
46/// with a visibility mask for each row.
47/// For instance, we could have a [`DataChunk`] of this format.
48///
49/// | v1 | v2 | v3 |
50/// |----|----|----|
51/// | 1  | a  | t  |
52/// | 2  | b  | f  |
53/// | 3  | c  | t  |
54/// | 4  | d  | f  |
55///
56/// Our columns are v1, v2, v3.
57/// Then, if the Visibility Mask hides rows 2 and 4,
58/// We will only have these rows visible:
59///
60/// | v1 | v2 | v3 |
61/// |----|----|----|
62/// | 1  | a  | t  |
63/// | 3  | c  | t  |
64#[derive(Clone, PartialEq)]
65#[must_use]
66pub struct DataChunk {
67    columns: Arc<[ArrayRef]>,
68    visibility: Bitmap,
69}
70
71impl DataChunk {
72    pub(crate) const PRETTY_TABLE_STYLE: TableStyle = TableStyle::new()
73        .top_border(LineStyle::new('+', '-', '+', '+'))
74        .header_lines(ContentLineStyle::new('|', '|', '|'))
75        .header_separator(LineStyle::new('+', '-', '+', '+'))
76        .content_lines(ContentLineStyle::new('|', '|', '|'))
77        .bottom_border(LineStyle::new('+', '-', '+', '+'));
78
79    /// Create a `DataChunk` with `columns` and visibility.
80    ///
81    /// The visibility can either be a `Bitmap` or a simple cardinality number.
82    pub fn new(columns: Vec<ArrayRef>, visibility: impl Into<Bitmap>) -> Self {
83        let visibility = visibility.into();
84        let capacity = visibility.len();
85        for column in &columns {
86            assert_eq!(capacity, column.len());
87        }
88
89        DataChunk {
90            columns: columns.into(),
91            visibility,
92        }
93    }
94
95    /// `new_dummy` creates a data chunk without columns but only a cardinality.
96    pub fn new_dummy(cardinality: usize) -> Self {
97        DataChunk {
98            columns: Arc::new([]),
99            visibility: Bitmap::ones(cardinality),
100        }
101    }
102
103    /// Build a `DataChunk` with rows.
104    ///
105    /// Panics if the `rows` is empty.
106    ///
107    /// Should prefer using [`DataChunkBuilder`] instead to avoid unnecessary allocation
108    /// of rows.
109    pub fn from_rows(rows: &[impl Row], data_types: &[DataType]) -> Self {
110        // `append_one_row` will cause the builder to finish immediately once capacity is met.
111        // Hence, we allocate an extra row here, to avoid the builder finishing prematurely.
112        // This just makes the code cleaner, since we can loop through all rows, and consume it finally.
113        // TODO: introduce `new_unlimited` to decouple memory reservation from builder capacity.
114        let mut builder = DataChunkBuilder::new(data_types.to_vec(), rows.len() + 1);
115
116        for row in rows {
117            let none = builder.append_one_row(row);
118            debug_assert!(none.is_none());
119        }
120
121        builder.consume_all().expect("chunk should not be empty")
122    }
123
124    /// Return the next visible row index on or after `row_idx`.
125    pub fn next_visible_row_idx(&self, row_idx: usize) -> Option<usize> {
126        self.visibility.next_set_bit(row_idx)
127    }
128
129    pub fn into_parts(self) -> (Vec<ArrayRef>, Bitmap) {
130        (self.columns.to_vec(), self.visibility)
131    }
132
133    pub fn into_parts_v2(self) -> (Arc<[ArrayRef]>, Bitmap) {
134        (self.columns, self.visibility)
135    }
136
137    pub fn from_parts(columns: Arc<[ArrayRef]>, visibilities: Bitmap) -> Self {
138        Self {
139            columns,
140            visibility: visibilities,
141        }
142    }
143
144    pub fn dimension(&self) -> usize {
145        self.columns.len()
146    }
147
148    // TODO(rc): shall we rename this to `visible_size`? I sometimes find this confused with `capacity`.
149    /// `cardinality` returns the number of visible tuples
150    pub fn cardinality(&self) -> usize {
151        self.visibility.count_ones()
152    }
153
154    /// Returns whether this chunk contains at least one visible row.
155    pub fn has_visible_rows(&self) -> bool {
156        self.visibility.any()
157    }
158
159    // Compute the required permits of this chunk for rate limiting.
160    pub fn rate_limit_permits(&self) -> u64 {
161        self.cardinality() as _
162    }
163
164    // TODO(rc): shall we rename this to `size`?
165    /// `capacity` returns physical length of any chunk column
166    pub fn capacity(&self) -> usize {
167        self.visibility.len()
168    }
169
170    pub fn selectivity(&self) -> f64 {
171        if self.visibility.is_empty() {
172            0.0
173        } else if self.visibility.all() {
174            1.0
175        } else {
176            self.visibility.count_ones() as f64 / self.visibility.len() as f64
177        }
178    }
179
180    pub fn with_visibility(&self, visibility: impl Into<Bitmap>) -> Self {
181        DataChunk {
182            columns: self.columns.clone(),
183            visibility: visibility.into(),
184        }
185    }
186
187    pub fn visibility(&self) -> &Bitmap {
188        &self.visibility
189    }
190
191    pub fn set_visibility(&mut self, visibility: Bitmap) {
192        assert_eq!(visibility.len(), self.capacity());
193        self.visibility = visibility;
194    }
195
196    /// Returns whether all rows in the chunk are visible, i.e., the chunk is compacted
197    /// in terms of row visibility.
198    pub fn is_vis_compacted(&self) -> bool {
199        self.visibility.all()
200    }
201
202    pub fn column_at(&self, idx: usize) -> &ArrayRef {
203        &self.columns[idx]
204    }
205
206    pub fn columns(&self) -> &[ArrayRef] {
207        &self.columns
208    }
209
210    /// Returns the data types of all columns.
211    pub fn data_types(&self) -> Vec<DataType> {
212        self.columns.iter().map(|col| col.data_type()).collect()
213    }
214
215    /// Divides one chunk into two at an column index.
216    ///
217    /// # Panics
218    ///
219    /// Panics if `idx > columns.len()`.
220    pub fn split_column_at(&self, idx: usize) -> (Self, Self) {
221        let (left, right) = self.columns.split_at(idx);
222        let left = DataChunk::new(left.to_vec(), self.visibility.clone());
223        let right = DataChunk::new(right.to_vec(), self.visibility.clone());
224        (left, right)
225    }
226
227    pub fn to_protobuf(&self) -> PbDataChunk {
228        assert!(self.visibility.all(), "must be compacted before transfer");
229        let mut proto = PbDataChunk {
230            cardinality: self.cardinality() as u32,
231            columns: Default::default(),
232        };
233        let column_ref = &mut proto.columns;
234        for array in &*self.columns {
235            column_ref.push(array.to_protobuf());
236        }
237        proto
238    }
239
240    /// Removes the invisible rows based on `visibility`. Returns a new compacted chunk
241    /// with all rows visible.
242    ///
243    /// `compact_vis` has trade-offs:
244    ///
245    /// Cost:
246    /// It has to rebuild the each column, meaning it will incur cost
247    /// of copying over bytes from the original column array to the new one.
248    ///
249    /// Benefit:
250    /// The main benefit is that the data chunk is smaller, taking up less memory.
251    /// We can also save the cost of iterating over many hidden rows.
252    pub fn compact_vis(self) -> Self {
253        if self.visibility.all() {
254            return self;
255        }
256        let cardinality = self.visibility.count_ones();
257        let columns = self
258            .columns
259            .iter()
260            .map(|col| {
261                let array = col;
262                array.compact_vis(&self.visibility, cardinality).into()
263            })
264            .collect::<Vec<_>>();
265        Self::new(columns, Bitmap::ones(cardinality))
266    }
267
268    /// Scatter a compacted chunk to a new chunk with the given visibility.
269    pub fn expand_vis(self, vis: Bitmap) -> Self {
270        let mut uncompact_builders: Vec<_> = self
271            .columns
272            .iter()
273            .map(|c| c.create_builder(vis.len()))
274            .collect();
275        let mut last_u = None;
276
277        for (idx, u) in vis.iter_ones().enumerate() {
278            // pad invisible rows with NULL
279            let zeros = if let Some(last_u) = last_u {
280                u - last_u - 1
281            } else {
282                u
283            };
284            for _ in 0..zeros {
285                uncompact_builders
286                    .iter_mut()
287                    .for_each(|builder| builder.append_null());
288            }
289            uncompact_builders
290                .iter_mut()
291                .zip_eq_fast(self.columns.iter())
292                .for_each(|(builder, c)| builder.append(c.datum_at(idx)));
293            last_u = Some(u);
294        }
295        let zeros = if let Some(last_u) = last_u {
296            vis.len() - last_u - 1
297        } else {
298            vis.len()
299        };
300        for _ in 0..zeros {
301            uncompact_builders
302                .iter_mut()
303                .for_each(|builder| builder.append_null());
304        }
305        let array: Vec<_> = uncompact_builders
306            .into_iter()
307            .map(|builder| Arc::new(builder.finish()))
308            .collect();
309
310        Self::new(array, vis)
311    }
312
313    pub fn from_protobuf(proto: &PbDataChunk) -> ArrayResult<Self> {
314        let mut columns = vec![];
315        for any_col in proto.get_columns() {
316            let cardinality = proto.get_cardinality() as usize;
317            columns.push(ArrayImpl::from_protobuf(any_col, cardinality)?.into());
318        }
319
320        let chunk = DataChunk::new(columns, proto.cardinality as usize);
321        Ok(chunk)
322    }
323
324    /// `rechunk` creates a new vector of data chunk whose size is `each_size_limit`.
325    /// When the total cardinality of all the chunks is not evenly divided by the `each_size_limit`,
326    /// the last new chunk will be the remainder.
327    pub fn rechunk(chunks: &[DataChunk], each_size_limit: usize) -> ArrayResult<Vec<DataChunk>> {
328        let Some(data_types) = chunks.first().map(|c| c.data_types()) else {
329            return Ok(Vec::new());
330        };
331
332        let mut builder = DataChunkBuilder::new(data_types, each_size_limit);
333        let mut outputs = Vec::new();
334
335        for chunk in chunks {
336            for output in builder.append_chunk(chunk.clone()) {
337                outputs.push(output);
338            }
339        }
340        if let Some(output) = builder.consume_all() {
341            outputs.push(output);
342        }
343
344        Ok(outputs)
345    }
346
347    /// Compute hash values for each row. The number of the returning `HashCodes` is `self.capacity()`.
348    /// When `skip_invisible_row` is true, the `HashCode` for the invisible rows is arbitrary.
349    pub fn get_hash_values<H: BuildHasher>(
350        &self,
351        column_idxes: &[usize],
352        hasher_builder: H,
353    ) -> Vec<HashCode<H>> {
354        let len = self.capacity();
355        let mut states = Vec::with_capacity(len);
356        states.resize_with(len, || hasher_builder.build_hasher());
357        // Compute hash for the specified columns.
358        for column_idx in column_idxes {
359            let array = self.column_at(*column_idx);
360            array.hash_vec(&mut states[..], self.visibility());
361        }
362        finalize_hashers(&states[..])
363            .into_iter()
364            .map(|hash_code| hash_code.into())
365            .collect_vec()
366    }
367
368    /// Random access a tuple in a data chunk. Return in a row format.
369    /// # Arguments
370    /// * `pos` - Index of look up tuple
371    /// * `RowRef` - Reference of data tuple
372    /// * bool - whether this tuple is visible
373    pub fn row_at(&self, pos: usize) -> (RowRef<'_>, bool) {
374        let row = self.row_at_unchecked_vis(pos);
375        let vis = self.visibility.is_set(pos);
376        (row, vis)
377    }
378
379    /// Random access a tuple in a data chunk. Return in a row format.
380    /// Note that this function do not return whether the row is visible.
381    /// # Arguments
382    /// * `pos` - Index of look up tuple
383    pub fn row_at_unchecked_vis(&self, pos: usize) -> RowRef<'_> {
384        RowRef::new(self, pos)
385    }
386
387    /// Returns a table-like text representation of the `DataChunk`.
388    pub fn to_pretty(&self) -> impl Display + use<> {
389        use comfy_table::Table;
390
391        if self.cardinality() == 0 {
392            return Either::Left("(empty)");
393        }
394
395        let mut table = Table::new();
396        table.load_style(Self::PRETTY_TABLE_STYLE);
397
398        for row in self.rows() {
399            let cells: Vec<_> = row
400                .iter()
401                .map(|v| {
402                    match v {
403                        None => "".to_owned(), // NULL
404                        Some(scalar) => scalar.to_text(),
405                    }
406                })
407                .collect();
408            table.add_row(cells);
409        }
410
411        Either::Right(table)
412    }
413
414    /// Keep the specified columns and set the rest elements to null.
415    ///
416    /// # Example
417    ///
418    /// ```text
419    /// i i i                            i i i
420    /// 1 2 3  --> keep_columns([1]) --> . 2 .
421    /// 4 5 6                            . 5 .
422    /// ```
423    pub fn keep_columns(&self, column_indices: &[usize]) -> Self {
424        let capacity: usize = self.capacity();
425        let columns = (self.columns.iter().enumerate())
426            .map(|(i, column)| {
427                if column_indices.contains(&i) {
428                    column.clone()
429                } else {
430                    let mut builder = column.create_builder(capacity);
431                    builder.append_n(capacity, None as DatumRef<'_>);
432                    builder.finish().into()
433                }
434            })
435            .collect();
436        DataChunk {
437            columns,
438            visibility: self.visibility.clone(),
439        }
440    }
441
442    /// Reorder (and possibly remove) columns.
443    ///
444    /// e.g. if `indices` is `[2, 1, 0]`, and the chunk contains column `[a, b, c]`, then the output
445    /// will be `[c, b, a]`. If `indices` is [2, 0], then the output will be `[c, a]`.
446    /// If the input mapping is identity mapping, no reorder will be performed.
447    pub fn project(&self, indices: &[usize]) -> Self {
448        Self {
449            columns: indices.iter().map(|i| self.columns[*i].clone()).collect(),
450            visibility: self.visibility.clone(),
451        }
452    }
453
454    /// Reorder columns and set visibility.
455    pub fn project_with_vis(&self, indices: &[usize], visibility: Bitmap) -> Self {
456        assert_eq!(visibility.len(), self.capacity());
457        Self {
458            columns: indices.iter().map(|i| self.columns[*i].clone()).collect(),
459            visibility,
460        }
461    }
462
463    /// Reorder rows by indexes.
464    pub fn reorder_rows(&self, indexes: &[usize]) -> Self {
465        let mut array_builders: Vec<ArrayBuilderImpl> = self
466            .columns
467            .iter()
468            .map(|col| col.create_builder(indexes.len()))
469            .collect();
470        for &i in indexes {
471            for (builder, col) in array_builders.iter_mut().zip_eq_fast(self.columns.iter()) {
472                builder.append(col.value_at(i));
473            }
474        }
475        let columns = array_builders
476            .into_iter()
477            .map(|builder| builder.finish().into())
478            .collect();
479        DataChunk::new(columns, indexes.len())
480    }
481
482    /// Partition fixed size datums and variable length ones.
483    /// ---
484    /// In some cases, we have fixed size for the entire column,
485    /// when the datatypes are fixed size or the datums are constants.
486    /// As such we can compute the size for it just once for the column.
487    ///
488    /// Otherwise, for variable sized datatypes, such as `varchar`,
489    /// we have to individually compute their sizes per row.
490    fn partition_sizes(&self) -> (usize, Vec<&ArrayRef>) {
491        let mut col_variable: Vec<&ArrayRef> = vec![];
492        let mut row_len_fixed: usize = 0;
493        for c in &*self.columns {
494            if let Some(field_len) = try_get_exact_serialize_datum_size(c) {
495                row_len_fixed += field_len;
496            } else {
497                col_variable.push(c);
498            }
499        }
500        (row_len_fixed, col_variable)
501    }
502
503    unsafe fn compute_size_of_variable_cols_in_row(
504        variable_cols: &[&ArrayRef],
505        row_idx: usize,
506    ) -> usize {
507        unsafe {
508            variable_cols
509                .iter()
510                .map(|col| estimate_serialize_datum_size(col.value_at_unchecked(row_idx)))
511                .sum::<usize>()
512        }
513    }
514
515    unsafe fn init_buffer(
516        row_len_fixed: usize,
517        variable_cols: &[&ArrayRef],
518        row_idx: usize,
519    ) -> Vec<u8> {
520        unsafe {
521            Vec::with_capacity(
522                row_len_fixed + Self::compute_size_of_variable_cols_in_row(variable_cols, row_idx),
523            )
524        }
525    }
526
527    /// Serialize each row into value encoding bytes.
528    ///
529    /// The returned vector's size is `self.capacity()` and for the invisible row will give a empty
530    /// bytes.
531    // Note(bugen): should we exclude the invisible rows in the output so that the caller won't need
532    // to handle visibility again?
533    pub fn serialize(&self) -> Vec<Bytes> {
534        let buffers = if !self.visibility.all() {
535            let rows_num = self.visibility.len();
536            let mut buffers: Vec<Vec<u8>> = vec![];
537            let (row_len_fixed, col_variable) = self.partition_sizes();
538
539            // First initialize buffer with the right size to avoid re-allocations
540            for i in 0..rows_num {
541                // SAFETY(value_at_unchecked): the idx is always in bound.
542                unsafe {
543                    if self.visibility.is_set_unchecked(i) {
544                        buffers.push(Self::init_buffer(row_len_fixed, &col_variable, i));
545                    } else {
546                        buffers.push(vec![]);
547                    }
548                }
549            }
550
551            // Then do the actual serialization
552            for c in &*self.columns {
553                assert_eq!(c.len(), rows_num);
554                for (i, buffer) in buffers.iter_mut().enumerate() {
555                    // SAFETY(value_at_unchecked): the idx is always in bound.
556                    unsafe {
557                        if self.visibility.is_set_unchecked(i) {
558                            serialize_datum_into(c.value_at_unchecked(i), buffer);
559                        }
560                    }
561                }
562            }
563            buffers
564        } else {
565            let mut buffers: Vec<Vec<u8>> = vec![];
566            let (row_len_fixed, col_variable) = self.partition_sizes();
567            for i in 0..self.visibility.len() {
568                unsafe {
569                    buffers.push(Self::init_buffer(row_len_fixed, &col_variable, i));
570                }
571            }
572            for c in &*self.columns {
573                assert_eq!(c.len(), self.visibility.len());
574                for (i, buffer) in buffers.iter_mut().enumerate() {
575                    // SAFETY(value_at_unchecked): the idx is always in bound.
576                    unsafe {
577                        serialize_datum_into(c.value_at_unchecked(i), buffer);
578                    }
579                }
580            }
581            buffers
582        };
583
584        buffers.into_iter().map(|item| item.into()).collect_vec()
585    }
586
587    /// Serialize each row into bytes with given serializer.
588    ///
589    /// This is similar to `serialize` but it uses a custom serializer. Prefer `serialize` if
590    /// possible since it might be more efficient due to columnar operations.
591    pub fn serialize_with(&self, serializer: &impl ValueRowSerializer) -> Vec<Bytes> {
592        let mut results = Vec::with_capacity(self.capacity());
593        for row in self.rows_with_holes() {
594            results.push(if let Some(row) = row {
595                serializer.serialize(row).into()
596            } else {
597                Bytes::new()
598            });
599        }
600        results
601    }
602
603    /// Estimate size of hash keys. Their indices in a row are indicated by `column_indices`.
604    /// Size here refers to the number of u8s required to store the serialized datum.
605    pub fn estimate_value_encoding_size(&self, column_indices: &[usize]) -> usize {
606        if self.capacity() == 0 {
607            0
608        } else {
609            column_indices
610                .iter()
611                .map(|idx| {
612                    let datum = self.column_at(*idx).datum_at(0);
613                    estimate_serialize_datum_size(datum)
614                })
615                .sum()
616        }
617    }
618}
619
620impl fmt::Debug for DataChunk {
621    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
622        write!(
623            f,
624            "DataChunk {{ cardinality = {}, capacity = {}, data = \n{} }}",
625            self.cardinality(),
626            self.capacity(),
627            self.to_pretty()
628        )
629    }
630}
631
632impl<'a> From<&'a StructArray> for DataChunk {
633    fn from(array: &'a StructArray) -> Self {
634        Self {
635            columns: array.fields().cloned().collect(),
636            visibility: Bitmap::ones(array.len()),
637        }
638    }
639}
640
641impl EstimateSize for DataChunk {
642    fn estimated_heap_size(&self) -> usize {
643        self.columns
644            .iter()
645            .map(|a| a.estimated_heap_size())
646            .sum::<usize>()
647            + self.visibility.estimated_heap_size()
648    }
649}
650
651/// Test utilities for [`DataChunk`].
652pub trait DataChunkTestExt {
653    /// SEED for generating data chunk.
654    const SEED: u64 = 0xFF67FEABBAEF76FF;
655
656    /// Parse a chunk from string.
657    ///
658    /// # Format
659    ///
660    /// The first line is a header indicating the column types.
661    /// The following lines indicate rows within the chunk.
662    /// Each line starts with an operation followed by values.
663    /// NULL values are represented as `.`.
664    ///
665    /// # Example
666    /// ```
667    /// use risingwave_common::array::{DataChunk, DataChunkTestExt};
668    /// let chunk = DataChunk::from_pretty(
669    ///     "I I I I      // type chars
670    ///      2 5 . .      // '.' means NULL
671    ///      2 5 2 6 D    // 'D' means deleted in visibility
672    ///      . . 4 8      // ^ comments are ignored
673    ///      . . 3 4",
674    /// );
675    ///
676    /// // type chars:
677    /// //     B: bool
678    /// //     I: i64
679    /// //     i: i32
680    /// //     F: f64
681    /// //     f: f32
682    /// //     T: str
683    /// //    TS: Timestamp
684    /// //   SRL: Serial
685    /// // <i,f>: struct
686    /// ```
687    fn from_pretty(s: &str) -> Self;
688
689    /// Insert one invisible hole after every record.
690    fn with_invisible_holes(self) -> Self
691    where
692        Self: Sized;
693
694    /// Panic if the chunk is invalid.
695    fn assert_valid(&self);
696
697    /// Generate data chunk when supplied with `chunk_size` and column data types.
698    fn gen_data_chunk(
699        chunk_offset: usize,
700        chunk_size: usize,
701        data_types: &[DataType],
702        string_properties: &VarcharProperty,
703        visibility_ratio: f64,
704    ) -> Self;
705
706    /// Generate data chunks when supplied with `chunk_size` and column data types.
707    fn gen_data_chunks(
708        num_of_chunks: usize,
709        chunk_size: usize,
710        data_types: &[DataType],
711        string_properties: &VarcharProperty,
712        visibility_ratio: f64,
713    ) -> Vec<Self>
714    where
715        Self: Sized;
716}
717
718impl DataChunkTestExt for DataChunk {
719    fn from_pretty(s: &str) -> Self {
720        use crate::types::ScalarImpl;
721        fn parse_type(s: &str) -> DataType {
722            if let Some(s) = s.strip_suffix("[]") {
723                return DataType::list(parse_type(s));
724            }
725
726            // Special logic to support Map type in `DataChunk::from_pretty`.
727            // Please refer to `src/expr/impl/src/scalar/map_filter.rs`.
728            if let Some(inner) = s.strip_prefix("map<").and_then(|s| s.strip_suffix('>')) {
729                let mut parts = inner.split(',');
730                let key_type = parts.next().expect("Key type expected");
731                let value_type = parts.next().expect("Value type expected");
732                return DataType::Map(MapType::from_kv(
733                    parse_type(key_type),
734                    parse_type(value_type),
735                ));
736            }
737
738            match s {
739                "B" => DataType::Boolean,
740                "I" => DataType::Int64,
741                "i" => DataType::Int32,
742                "F" => DataType::Float64,
743                "f" => DataType::Float32,
744                "TS" => DataType::Timestamp,
745                "TZ" => DataType::Timestamptz,
746                "T" => DataType::Varchar,
747                "SRL" => DataType::Serial,
748                "D" => DataType::Date,
749                array if array.starts_with('<') && array.ends_with('>') => DataType::Struct(
750                    StructType::unnamed(array[1..array.len() - 1].split(',').map(parse_type)),
751                ),
752                _ => todo!("unsupported type: {s:?}"),
753            }
754        }
755
756        let mut lines = s.split('\n').filter(|l| !l.trim().is_empty());
757        // initialize array builders from the first line
758        let header = lines.next().unwrap().trim();
759        let datatypes = header
760            .split_ascii_whitespace()
761            .take_while(|c| *c != "//")
762            .map(parse_type)
763            .collect::<Vec<_>>();
764        let mut array_builders = datatypes
765            .iter()
766            .map(|ty| ty.create_array_builder(1))
767            .collect::<Vec<_>>();
768        let mut visibility = vec![];
769        for line in lines {
770            let mut token = line.trim().split_ascii_whitespace();
771            // allow `zip` since `token` may longer than `array_builders`
772            #[expect(clippy::disallowed_methods)]
773            for ((builder, ty), val_str) in
774                array_builders.iter_mut().zip(&datatypes).zip(&mut token)
775            {
776                let datum = match val_str {
777                    "." => None,
778                    "(empty)" => Some("".into()),
779                    // `from_text_for_test` has support for Map.
780                    _ => Some(ScalarImpl::from_text_for_test(val_str, ty).unwrap()),
781                };
782                builder.append(datum);
783            }
784            let visible = match token.next() {
785                None | Some("//") => true,
786                Some("D") => false,
787                Some(t) => panic!("invalid token: {t:?}"),
788            };
789            visibility.push(visible);
790        }
791        let columns = array_builders
792            .into_iter()
793            .map(|builder| builder.finish().into())
794            .collect();
795        let vis = Bitmap::from_iter(visibility);
796        let chunk = DataChunk::new(columns, vis);
797        chunk.assert_valid();
798        chunk
799    }
800
801    fn with_invisible_holes(self) -> Self
802    where
803        Self: Sized,
804    {
805        let (cols, vis) = self.into_parts();
806        let n = vis.len();
807        let mut new_vis = BitmapBuilder::with_capacity(n * 2);
808        for b in vis.iter() {
809            new_vis.append(b);
810            new_vis.append(false);
811        }
812        let new_cols = cols
813            .into_iter()
814            .map(|col| {
815                let arr = col;
816                let mut builder = arr.create_builder(n * 2);
817                for v in arr.iter() {
818                    builder.append(v.to_owned_datum());
819                    builder.append_null();
820                }
821
822                builder.finish().into()
823            })
824            .collect();
825        let chunk = DataChunk::new(new_cols, new_vis.finish());
826        chunk.assert_valid();
827        chunk
828    }
829
830    fn assert_valid(&self) {
831        let cols = self.columns();
832        let vis = &self.visibility;
833        let n = vis.len();
834        for col in cols {
835            assert_eq!(col.len(), n);
836        }
837    }
838
839    fn gen_data_chunk(
840        chunk_offset: usize,
841        chunk_size: usize,
842        data_types: &[DataType],
843        varchar_properties: &VarcharProperty,
844        visibility_percent: f64,
845    ) -> Self {
846        let vis = if visibility_percent == 0.0 {
847            Bitmap::zeros(chunk_size)
848        } else if visibility_percent == 1.0 {
849            Bitmap::ones(chunk_size)
850        } else {
851            let mut rng = SmallRng::from_seed([0; 32]);
852            let mut vis_builder = BitmapBuilder::with_capacity(chunk_size);
853            for _i in 0..chunk_size {
854                vis_builder.append(rng.random_bool(visibility_percent));
855            }
856            vis_builder.finish()
857        };
858
859        let mut columns = Vec::new();
860        // Generate columns of this chunk.
861        for data_type in data_types {
862            let mut array_builder = data_type.create_array_builder(chunk_size);
863            for j in 0..chunk_size {
864                let offset = ((chunk_offset + 1) * (j + 1)) as u64;
865                match data_type {
866                    DataType::Varchar => {
867                        let datum =
868                            FieldGeneratorImpl::with_varchar(varchar_properties, Self::SEED)
869                                .generate_datum(offset);
870                        array_builder.append(&datum);
871                    }
872                    DataType::Timestamp => {
873                        let datum =
874                            FieldGeneratorImpl::with_timestamp(None, None, None, Self::SEED)
875                                .expect("create timestamp generator should succeed")
876                                .generate_datum(offset);
877                        array_builder.append(datum);
878                    }
879                    DataType::Timestamptz => {
880                        let datum =
881                            FieldGeneratorImpl::with_timestamptz(None, None, None, Self::SEED)
882                                .expect("create timestamptz generator should succeed")
883                                .generate_datum(offset);
884                        array_builder.append(datum);
885                    }
886                    _ if data_type.is_numeric() => {
887                        let mut data_gen = FieldGeneratorImpl::with_number_random(
888                            data_type.clone(),
889                            None,
890                            None,
891                            Self::SEED,
892                        )
893                        .unwrap();
894                        let datum = data_gen.generate_datum(offset);
895                        array_builder.append(datum);
896                    }
897                    _ => todo!("unsupported type: {data_type:?}"),
898                }
899            }
900            columns.push(array_builder.finish().into());
901        }
902        DataChunk::new(columns, vis)
903    }
904
905    fn gen_data_chunks(
906        num_of_chunks: usize,
907        chunk_size: usize,
908        data_types: &[DataType],
909        varchar_properties: &VarcharProperty,
910        visibility_percent: f64,
911    ) -> Vec<Self> {
912        (0..num_of_chunks)
913            .map(|i| {
914                Self::gen_data_chunk(
915                    i,
916                    chunk_size,
917                    data_types,
918                    varchar_properties,
919                    visibility_percent,
920                )
921            })
922            .collect()
923    }
924}
925
926#[cfg(test)]
927mod tests {
928    use crate::array::*;
929    use crate::row::Row;
930
931    #[test]
932    fn test_rechunk() {
933        let test_case = |num_chunks: usize, chunk_size: usize, new_chunk_size: usize| {
934            let mut chunks = vec![];
935            for chunk_idx in 0..num_chunks {
936                let mut builder = PrimitiveArrayBuilder::<i32>::new(0);
937                for i in chunk_size * chunk_idx..chunk_size * (chunk_idx + 1) {
938                    builder.append(Some(i as i32));
939                }
940                let chunk = DataChunk::new(vec![Arc::new(builder.finish().into())], chunk_size);
941                chunks.push(chunk);
942            }
943
944            let total_size = num_chunks * chunk_size;
945            let num_full_new_chunk = total_size / new_chunk_size;
946            let mut chunk_sizes = vec![new_chunk_size; num_full_new_chunk];
947            let remainder = total_size % new_chunk_size;
948            if remainder != 0 {
949                chunk_sizes.push(remainder);
950            }
951
952            let new_chunks = DataChunk::rechunk(&chunks, new_chunk_size).unwrap();
953            assert_eq!(new_chunks.len(), chunk_sizes.len());
954            // check cardinality
955            for (idx, chunk_size) in chunk_sizes.iter().enumerate() {
956                assert_eq!(*chunk_size, new_chunks[idx].capacity());
957            }
958
959            let mut chunk_idx = 0;
960            let mut cur_idx = 0;
961            for val in 0..total_size {
962                if cur_idx >= chunk_sizes[chunk_idx] {
963                    cur_idx = 0;
964                    chunk_idx += 1;
965                }
966                assert_eq!(
967                    new_chunks[chunk_idx]
968                        .column_at(0)
969                        .as_int32()
970                        .value_at(cur_idx)
971                        .unwrap(),
972                    val as i32
973                );
974                cur_idx += 1;
975            }
976        };
977
978        test_case(0, 0, 1);
979        test_case(0, 10, 1);
980        test_case(10, 0, 1);
981        test_case(1, 1, 6);
982        test_case(1, 10, 11);
983        test_case(2, 3, 6);
984        test_case(5, 5, 6);
985        test_case(10, 10, 7);
986    }
987
988    #[test]
989    fn test_chunk_iter() {
990        let num_of_columns: usize = 2;
991        let length = 5;
992        let mut columns = vec![];
993        for i in 0..num_of_columns {
994            let mut builder = PrimitiveArrayBuilder::<i32>::new(length);
995            for _ in 0..length {
996                builder.append(Some(i as i32));
997            }
998            let arr = builder.finish();
999            columns.push(Arc::new(arr.into()))
1000        }
1001        let chunk: DataChunk = DataChunk::new(columns, length);
1002        for row in chunk.rows() {
1003            for i in 0..num_of_columns {
1004                let val = row.datum_at(i).unwrap();
1005                assert_eq!(val.into_int32(), i as i32);
1006            }
1007        }
1008    }
1009
1010    #[test]
1011    fn test_to_pretty_string() {
1012        let chunk = DataChunk::new(
1013            vec![
1014                Arc::new(I64Array::from_iter([1, 2, 3, 4]).into()),
1015                Arc::new(I64Array::from_iter([Some(6), None, Some(7), None]).into()),
1016            ],
1017            4,
1018        );
1019        assert_eq!(
1020            chunk.to_pretty().to_string(),
1021            "\
1022+---+---+
1023| 1 | 6 |
1024| 2 |   |
1025| 3 | 7 |
1026| 4 |   |
1027+---+---+"
1028        );
1029    }
1030
1031    #[test]
1032    fn test_no_column_chunk() {
1033        let chunk = DataChunk::new_dummy(10);
1034        assert_eq!(chunk.rows().count(), 10);
1035
1036        let chunk_after_serde = DataChunk::from_protobuf(&chunk.to_protobuf()).unwrap();
1037        assert_eq!(chunk_after_serde.rows().count(), 10);
1038        assert_eq!(chunk_after_serde.cardinality(), 10);
1039    }
1040
1041    #[test]
1042    fn reorder_columns() {
1043        let chunk = DataChunk::from_pretty(
1044            "I I I
1045             2 5 1
1046             4 9 2
1047             6 9 3",
1048        );
1049        assert_eq!(
1050            chunk.project(&[2, 1, 0]),
1051            DataChunk::from_pretty(
1052                "I I I
1053                 1 5 2
1054                 2 9 4
1055                 3 9 6",
1056            )
1057        );
1058        assert_eq!(
1059            chunk.project(&[2, 0]),
1060            DataChunk::from_pretty(
1061                "I I
1062                 1 2
1063                 2 4
1064                 3 6",
1065            )
1066        );
1067        assert_eq!(chunk.project(&[0, 1, 2]), chunk);
1068        assert_eq!(chunk.project(&[]).cardinality(), 3);
1069    }
1070
1071    #[test]
1072    fn test_chunk_estimated_size() {
1073        assert_eq!(
1074            72,
1075            DataChunk::from_pretty(
1076                "I I I
1077                 1 5 2
1078                 2 9 4
1079                 3 9 6",
1080            )
1081            .estimated_heap_size()
1082        );
1083        assert_eq!(
1084            48,
1085            DataChunk::from_pretty(
1086                "I I
1087                 1 2
1088                 2 4
1089                 3 6",
1090            )
1091            .estimated_heap_size()
1092        );
1093    }
1094}