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